Merge pull request #518 from schungx/master

Add rustyline to rhai-repl and variable definition filter.
This commit is contained in:
Stephen Chung 2022-02-04 23:21:19 +08:00 committed by GitHub
commit c7888ba1f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1577 additions and 790 deletions

View File

@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: rustup toolchain update nightly && rustup default nightly - run: rustup toolchain update nightly && rustup default nightly
- name: Run benchmark - name: Run benchmark
run: cargo +nightly bench | tee output.txt run: cargo +nightly bench --features decimal,metadata,serde,debugging | tee output.txt
- name: Store benchmark result - name: Store benchmark result
uses: rhysd/github-action-benchmark@v1 uses: rhysd/github-action-benchmark@v1
with: with:

View File

@ -12,25 +12,57 @@ Bug fixes
* In `Scope::clone_visible`, constants are now properly cloned as constants. * In `Scope::clone_visible`, constants are now properly cloned as constants.
* Variables introduced inside `try` blocks are now properly cleaned up upon an exception. * Variables introduced inside `try` blocks are now properly cleaned up upon an exception.
* Off-by-one error in character positions after a comment line is now fixed. * Off-by-one error in character positions after a comment line is now fixed.
* Globally-defined constants are now encapsulated correctly inside a loaded module and no longer spill across call boundaries.
* Type names display is fixed.
Script-breaking changes Script-breaking changes
----------------------- -----------------------
* For consistency, the `export` statement no longer exports multiple variables. * For consistency with the `import` statement, the `export` statement no longer exports multiple variables.
New features New features
------------ ------------
* A debugging interface is added. * A debugging interface is added.
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface. * 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. * A new package, `DebuggingPackage`, is added which contains the `back_trace` function to get the current call stack anywhere in a script.
* `Engine::set_allow_shadowing` is added to allow/disallow variables _shadowing_, with new errors `EvalAltResult::ErrorVariableExists` and `ParseErrorType::VariableExists`.
* `Engine::on_def_var` allows registering a closure which can decide whether a variable definition is allow to continue, or should fail with an error.
Enhancements Enhancements
------------ ------------
* `rhai-repl` tool has a few more commands, such as `strict` to turn on/off _Strict Variables Mode_ and `optimize` to turn on/off script optimization.
* Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required. * Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required.
* The `no_module` feature now eliminates large sections of code via feature gates. * The `no_module` feature now eliminates large sections of code via feature gates.
* Debug display of `AST` is improved.
* `NativeCallContext::call_level()` is added to give the current nesting level of function calls.
* A new feature, `bin-features`, pulls in all the required features for `bin` tools.
* `AST` position display is improved:
* `Expr::start_position` is added to give the beginning of the expression (not the operator's position).
* `StmtBlock` and `Stmt::Block` now keep the position of the closing `}` as well.
REPL tool changes
-----------------
The REPL bin tool, `rhai-rpl`, has been enhanced.
### Build changes
* The `rustyline` feature is now required in order to build `rhai-repl`.
* Therefore, `rhai-repl` is no longer automatically built when using a simple `cargo build` with default features.
### Line editor
* `rhai-repl` now uses [`rustyline`](https://crates.io/crates/rustyline) as a line editor with history.
* Shift-Enter can now be used to enter multiple lines without having to attach the `\` continuation character the end of each line.
### New commands
* `strict` to turn on/off _Strict Variables Mode_.
* `optimize` to turn on/off script optimization.
* `history` to print lines history.
* `!!`, `!`_num_ and `!`_text_ to recall a history line.
* `keys` to print all key bindings.
Version 1.4.1 Version 1.4.1
@ -551,7 +583,7 @@ Breaking changes
New features New features
------------ ------------
* Line continuation (via `\`) and multi-line literal strings (wrapped with <code>\`</code>) support are added. * Line continuation (via `\`) and multi-line literal strings (wrapped with `` ` ``) support are added.
* Rhai scripts can now start with a shebang `#!` which is ignored. * Rhai scripts can now start with a shebang `#!` which is ignored.
Enhancements Enhancements

View File

@ -55,12 +55,16 @@ unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for ident
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata
debugging = ["internals"] # enable debugging debugging = ["internals"] # enable debugging
# compiling for no-std
no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"] no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"]
# compiling for WASM # compiling for WASM
wasm-bindgen = ["instant/wasm-bindgen"] wasm-bindgen = ["instant/wasm-bindgen"]
stdweb = ["instant/stdweb"] stdweb = ["instant/stdweb"]
# compiling bin tools
bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"]
[[bin]] [[bin]]
name = "rhai-repl" name = "rhai-repl"
required-features = ["rustyline"] required-features = ["rustyline"]

View File

@ -190,8 +190,8 @@ impl Engine {
&mut this_ptr, &mut this_ptr,
fn_def, fn_def,
&mut args, &mut args,
Position::NONE,
rewind_scope, rewind_scope,
Position::NONE,
0, 0,
); );

View File

@ -15,12 +15,12 @@ impl Engine {
/// > `Fn(name: &str, index: usize, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>` /// > `Fn(name: &str, index: usize, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>`
/// ///
/// where: /// where:
/// * `name`: name of the variable.
/// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the /// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the
/// variable is supposed to reside. Offsets start from 1, with 1 meaning the last variable in /// variable is supposed to reside. Offsets start from 1, with 1 meaning the last variable in
/// the current [`Scope`][crate::Scope]. Essentially the correct variable is at position /// the current [`Scope`][crate::Scope]. Essentially the correct variable is at position
/// `scope.len() - index`. If `index` is zero, then there is no pre-calculated offset position /// `scope.len() - index`. If `index` is zero, then there is no pre-calculated offset position
/// and a search through the current [`Scope`][crate::Scope] must be performed. /// and a search through the current [`Scope`][crate::Scope] must be performed.
///
/// * `context`: the current [evaluation context][`EvalContext`]. /// * `context`: the current [evaluation context][`EvalContext`].
/// ///
/// ## Return value /// ## Return value
@ -63,6 +63,67 @@ impl Engine {
self.resolve_var = Some(Box::new(callback)); self.resolve_var = Some(Box::new(callback));
self self
} }
/// Provide a callback that will be invoked before the definition of each variable .
///
/// # Callback Function Signature
///
/// The callback function signature takes the following form:
///
/// > `Fn(name: &str, is_const: bool, block_level: usize, will_shadow: bool, context: &EvalContext) -> Result<bool, Box<EvalAltResult>>`
///
/// where:
/// * `name`: name of the variable to be defined.
/// * `is_const`: `true` if the statement is `const`, otherwise it is `let`.
/// * `block_level`: the current nesting level of statement blocks, with zero being the global level
/// * `will_shadow`: will the variable _shadow_ an existing variable?
/// * `context`: the current [evaluation context][`EvalContext`].
///
/// ## Return value
///
/// * `Ok(true)`: continue with normal variable definition.
/// * `Ok(false)`: deny the variable definition with an [runtime error][EvalAltResult::ErrorRuntime].
///
/// ## Raising errors
///
/// Return `Err(...)` if there is an error.
///
/// # Example
///
/// ```should_panic
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// // Register a variable definition filter.
/// engine.on_def_var(|name, is_const, _, _, _| {
/// // Disallow defining MYSTIC_NUMBER as a constant
/// if name == "MYSTIC_NUMBER" && is_const {
/// Ok(false)
/// } else {
/// Ok(true)
/// }
/// });
///
/// // The following runs fine:
/// engine.eval::<i64>("let MYSTIC_NUMBER = 42;")?;
///
/// // The following will cause an error:
/// engine.eval::<i64>("const MYSTIC_NUMBER = 42;")?;
///
/// # Ok(())
/// # }
/// ```
#[inline(always)]
pub fn on_def_var(
&mut self,
callback: impl Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool>
+ SendSync
+ 'static,
) -> &mut Self {
self.def_var_filter = Some(Box::new(callback));
self
}
/// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens. /// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
@ -264,11 +325,12 @@ impl Engine {
/// Exported under the `debugging` feature only. /// Exported under the `debugging` feature only.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
#[inline(always)] #[inline(always)]
pub fn on_debugger( pub fn register_debugger(
&mut self, &mut self,
init: impl Fn() -> Dynamic + SendSync + 'static, init: impl Fn() -> Dynamic + SendSync + 'static,
callback: impl Fn( callback: impl Fn(
&mut EvalContext, &mut EvalContext,
crate::eval::DebuggerEvent,
crate::ast::ASTNode, crate::ast::ASTNode,
Option<&str>, Option<&str>,
Position, Position,

View File

@ -1,5 +1,7 @@
//! Module defining the public API of the Rhai engine. //! Module defining the public API of the Rhai engine.
pub mod type_names;
pub mod eval; pub mod eval;
pub mod run; pub mod run;

View File

@ -17,9 +17,11 @@ pub struct LanguageOptions {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub allow_anonymous_fn: bool, pub allow_anonymous_fn: bool,
/// Is looping allowed? /// Is looping allowed?
pub allow_loop: bool, pub allow_looping: bool,
/// Strict variables mode? /// Strict variables mode?
pub strict_var: bool, pub strict_var: bool,
/// Is variables shadowing allowed?
pub allow_shadowing: bool,
} }
impl LanguageOptions { impl LanguageOptions {
@ -32,8 +34,9 @@ impl LanguageOptions {
allow_stmt_expr: true, allow_stmt_expr: true,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
allow_anonymous_fn: true, allow_anonymous_fn: true,
allow_loop: true, allow_looping: true,
strict_var: false, strict_var: false,
allow_shadowing: true,
} }
} }
} }
@ -94,12 +97,12 @@ impl Engine {
/// Is looping allowed? /// Is looping allowed?
#[inline(always)] #[inline(always)]
pub fn allow_looping(&self) -> bool { pub fn allow_looping(&self) -> bool {
self.options.allow_loop self.options.allow_looping
} }
/// Set whether looping is allowed. /// Set whether looping is allowed.
#[inline(always)] #[inline(always)]
pub fn set_allow_looping(&mut self, enable: bool) { pub fn set_allow_looping(&mut self, enable: bool) {
self.options.allow_loop = enable; self.options.allow_looping = enable;
} }
/// Is strict variables mode enabled? /// Is strict variables mode enabled?
#[inline(always)] #[inline(always)]
@ -111,4 +114,14 @@ impl Engine {
pub fn set_strict_variables(&mut self, enable: bool) { pub fn set_strict_variables(&mut self, enable: bool) {
self.options.strict_var = enable; self.options.strict_var = enable;
} }
/// Is variables shadowing allowed?
#[inline(always)]
pub fn allow_shadowing(&self) -> bool {
self.options.allow_shadowing
}
/// Set whether variables shadowing is allowed.
#[inline(always)]
pub fn set_allow_shadowing(&mut self, enable: bool) {
self.options.allow_shadowing = enable;
}
} }

131
src/api/type_names.rs Normal file
View File

@ -0,0 +1,131 @@
use crate::{
Engine, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, Position, RhaiError, ERR,
};
use std::any::type_name;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// Map the name of a standard type into a friendly form.
#[inline]
#[must_use]
fn map_std_type_name(name: &str, shorthands: bool) -> &str {
let name = name.trim();
if name == type_name::<String>() {
return if shorthands { "string" } else { "String" };
}
if name == type_name::<ImmutableString>() || name == "ImmutableString" {
return if shorthands {
"string"
} else {
"ImmutableString"
};
}
if name == type_name::<&str>() {
return if shorthands { "string" } else { "&str" };
}
#[cfg(feature = "decimal")]
if name == type_name::<rust_decimal::Decimal>() {
return if shorthands { "decimal" } else { "Decimal" };
}
if name == type_name::<FnPtr>() || name == "FnPtr" {
return if shorthands { "Fn" } else { "FnPtr" };
}
#[cfg(not(feature = "no_index"))]
if name == type_name::<crate::Array>() || name == "Array" {
return if shorthands { "array" } else { "Array" };
}
#[cfg(not(feature = "no_index"))]
if name == type_name::<crate::Blob>() || name == "Blob" {
return if shorthands { "blob" } else { "Blob" };
}
#[cfg(not(feature = "no_object"))]
if name == type_name::<crate::Map>() || name == "Map" {
return if shorthands { "map" } else { "Map" };
}
#[cfg(not(feature = "no_std"))]
if name == type_name::<crate::Instant>() || name == "Instant" {
return if shorthands { "timestamp" } else { "Instant" };
}
if name == type_name::<ExclusiveRange>() || name == "ExclusiveRange" {
return if shorthands {
"range"
} else if cfg!(feature = "only_i32") {
"Range<i32>"
} else {
"Range<i64>"
};
}
if name == type_name::<InclusiveRange>() || name == "InclusiveRange" {
return if shorthands {
"range="
} else if cfg!(feature = "only_i32") {
"RangeInclusive<i32>"
} else {
"RangeInclusive<i64>"
};
}
if name.starts_with("rhai::") {
map_std_type_name(&name[6..], shorthands)
} else {
name
}
}
impl Engine {
/// Pretty-print a type name.
///
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
/// the type name provided for the registration will be used.
///
/// # Panics
///
/// Panics if the type name is `&mut`.
#[inline]
#[must_use]
pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names
.get(name)
.map(|s| s.as_str())
.unwrap_or_else(|| map_std_type_name(name, true))
}
/// Format a type name.
///
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
/// the type name provided for the registration will be used.
#[cfg(feature = "metadata")]
#[inline]
#[must_use]
pub(crate) fn format_type_name<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> {
if name.starts_with("&mut ") {
let x = &name[5..];
let r = self.format_type_name(x);
return if x != r {
format!("&mut {}", r).into()
} else {
name.into()
};
}
self.type_names
.get(name)
.map(|s| s.as_str())
.unwrap_or_else(|| match name {
"INT" => return type_name::<crate::INT>(),
#[cfg(not(feature = "no_float"))]
"FLOAT" => return type_name::<crate::FLOAT>(),
_ => map_std_type_name(name, false),
})
.into()
}
/// Make a `Box<`[`EvalAltResult<ErrorMismatchDataType>`][ERR::ErrorMismatchDataType]`>`.
#[inline(never)]
#[must_use]
pub(crate) fn make_type_mismatch_err<T>(&self, typ: &str, pos: Position) -> RhaiError {
ERR::ErrorMismatchDataType(self.map_type_name(type_name::<T>()).into(), typ.into(), pos)
.into()
}
}

View File

@ -1,10 +1,11 @@
//! Module defining the AST (abstract syntax tree). //! Module defining the AST (abstract syntax tree).
use super::{Expr, FnAccess, Stmt, StmtBlock, AST_OPTION_FLAGS}; use super::{Expr, FnAccess, Stmt, StmtBlock, AST_OPTION_FLAGS::*};
use crate::{Dynamic, FnNamespace, Identifier, Position, StaticVec}; use crate::{Dynamic, FnNamespace, Identifier, Position, StaticVec};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
fmt,
hash::Hash, hash::Hash,
ops::{Add, AddAssign}, ops::{Add, AddAssign},
}; };
@ -14,7 +15,7 @@ use std::{
/// # Thread Safety /// # Thread Safety
/// ///
/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. /// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct AST { pub struct AST {
/// Source of the [`AST`]. /// Source of the [`AST`].
/// No source if string is empty. /// No source if string is empty.
@ -23,7 +24,7 @@ pub struct AST {
body: StmtBlock, body: StmtBlock,
/// Script-defined functions. /// Script-defined functions.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
functions: crate::Shared<crate::Module>, lib: crate::Shared<crate::Module>,
/// Embedded module resolver, if any. /// Embedded module resolver, if any.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: Option<crate::Shared<crate::module::resolvers::StaticModuleResolver>>, resolver: Option<crate::Shared<crate::module::resolvers::StaticModuleResolver>>,
@ -36,6 +37,31 @@ impl Default for AST {
} }
} }
impl fmt::Debug for AST {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut fp = f.debug_struct("AST");
if !self.source.is_empty() {
fp.field("source: ", &self.source);
}
#[cfg(not(feature = "no_module"))]
if let Some(ref resolver) = self.resolver {
fp.field("resolver: ", resolver);
}
fp.field("body", &self.body.as_slice());
#[cfg(not(feature = "no_function"))]
if !self.lib.is_empty() {
for (_, _, _, _, ref fn_def) in self.lib.iter_script_fn() {
let sig = fn_def.to_string();
fp.field(&sig, &fn_def.body.as_slice());
}
}
fp.finish()
}
}
impl AST { impl AST {
/// Create a new [`AST`]. /// Create a new [`AST`].
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
@ -47,9 +73,9 @@ impl AST {
) -> Self { ) -> Self {
Self { Self {
source: Identifier::new_const(), source: Identifier::new_const(),
body: StmtBlock::new(statements, Position::NONE), body: StmtBlock::new(statements, Position::NONE, Position::NONE),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
functions: functions.into(), lib: functions.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: None, resolver: None,
} }
@ -65,9 +91,9 @@ impl AST {
) -> Self { ) -> Self {
Self { Self {
source: Identifier::new_const(), source: Identifier::new_const(),
body: StmtBlock::new(statements, Position::NONE), body: StmtBlock::new(statements, Position::NONE, Position::NONE),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
functions: functions.into(), lib: functions.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: None, resolver: None,
} }
@ -115,7 +141,7 @@ impl AST {
source: Identifier::new_const(), source: Identifier::new_const(),
body: StmtBlock::NONE, body: StmtBlock::NONE,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
functions: crate::Module::new().into(), lib: crate::Module::new().into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: None, resolver: None,
} }
@ -140,7 +166,7 @@ impl AST {
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self { pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
let source = source.into(); let source = source.into();
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
crate::Shared::get_mut(&mut self.functions) crate::Shared::get_mut(&mut self.lib)
.as_mut() .as_mut()
.map(|m| m.set_id(source.clone())); .map(|m| m.set_id(source.clone()));
self.source = source; self.source = source;
@ -181,7 +207,7 @@ impl AST {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn has_functions(&self) -> bool { pub fn has_functions(&self) -> bool {
!self.functions.is_empty() !self.lib.is_empty()
} }
/// Get the internal shared [`Module`][crate::Module] containing all script-defined functions. /// Get the internal shared [`Module`][crate::Module] containing all script-defined functions.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
@ -189,7 +215,7 @@ impl AST {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn shared_lib(&self) -> &crate::Shared<crate::Module> { pub(crate) fn shared_lib(&self) -> &crate::Shared<crate::Module> {
&self.functions &self.lib
} }
/// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions. /// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -200,7 +226,7 @@ impl AST {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn shared_lib(&self) -> &crate::Shared<crate::Module> { pub fn shared_lib(&self) -> &crate::Shared<crate::Module> {
&self.functions &self.lib
} }
/// Get the embedded [module resolver][`ModuleResolver`]. /// Get the embedded [module resolver][`ModuleResolver`].
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
@ -260,12 +286,12 @@ impl AST {
&self, &self,
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> Self { ) -> Self {
let mut functions = crate::Module::new(); let mut lib = crate::Module::new();
functions.merge_filtered(&self.functions, &filter); lib.merge_filtered(&self.lib, &filter);
Self { Self {
source: self.source.clone(), source: self.source.clone(),
body: StmtBlock::NONE, body: StmtBlock::NONE,
functions: functions.into(), lib: lib.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: self.resolver.clone(), resolver: self.resolver.clone(),
} }
@ -279,7 +305,7 @@ impl AST {
source: self.source.clone(), source: self.source.clone(),
body: self.body.clone(), body: self.body.clone(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
functions: crate::Module::new().into(), lib: crate::Module::new().into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: self.resolver.clone(), resolver: self.resolver.clone(),
} }
@ -472,24 +498,24 @@ impl AST {
}; };
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let functions = { let lib = {
let mut functions = self.functions.as_ref().clone(); let mut lib = self.lib.as_ref().clone();
functions.merge_filtered(&other.functions, &_filter); lib.merge_filtered(&other.lib, &_filter);
functions lib
}; };
if !other.source.is_empty() { if !other.source.is_empty() {
Self::new_with_source( Self::new_with_source(
merged, merged,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
functions, lib,
other.source.clone(), other.source.clone(),
) )
} else { } else {
Self::new( Self::new(
merged, merged,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
functions, lib,
) )
} }
} }
@ -562,9 +588,9 @@ impl AST {
self.body.extend(other.body.into_iter()); self.body.extend(other.body.into_iter());
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if !other.functions.is_empty() { if !other.lib.is_empty() {
crate::func::native::shared_make_mut(&mut self.functions) crate::func::native::shared_make_mut(&mut self.lib)
.merge_filtered(&other.functions, &_filter); .merge_filtered(&other.lib, &_filter);
} }
self self
} }
@ -599,20 +625,32 @@ impl AST {
&mut self, &mut self,
filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool,
) -> &mut Self { ) -> &mut Self {
if !self.functions.is_empty() { if !self.lib.is_empty() {
crate::func::native::shared_make_mut(&mut self.functions) crate::func::native::shared_make_mut(&mut self.lib).retain_script_functions(filter);
.retain_script_functions(filter);
} }
self self
} }
/// _(internals)_ Iterate through all function definitions.
/// Exported under the `internals` feature only.
///
/// Not available under `no_function`.
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_function"))]
#[inline]
pub fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
self.lib
.iter_script_fn()
.map(|(_, _, _, _, fn_def)| fn_def.as_ref())
}
/// Iterate through all function definitions. /// Iterate through all function definitions.
/// ///
/// Not available under `no_function`. /// Not available under `no_function`.
#[cfg(not(feature = "internals"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> { pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
self.functions self.lib
.iter_script_fn() .iter_script_fn()
.map(|(_, _, _, _, fn_def)| fn_def.as_ref()) .map(|(_, _, _, _, fn_def)| fn_def.as_ref())
} }
@ -622,7 +660,7 @@ impl AST {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline]
pub fn iter_functions<'a>(&'a self) -> impl Iterator<Item = super::ScriptFnMetadata> + 'a { pub fn iter_functions<'a>(&'a self) -> impl Iterator<Item = super::ScriptFnMetadata> + 'a {
self.functions self.lib
.iter_script_fn() .iter_script_fn()
.map(|(_, _, _, _, fn_def)| fn_def.as_ref().into()) .map(|(_, _, _, _, fn_def)| fn_def.as_ref().into())
} }
@ -632,7 +670,7 @@ impl AST {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn clear_functions(&mut self) -> &mut Self { pub fn clear_functions(&mut self) -> &mut Self {
self.functions = crate::Module::new().into(); self.lib = crate::Module::new().into();
self self
} }
/// Clear all statements in the [`AST`], leaving only function definitions. /// Clear all statements in the [`AST`], leaving only function definitions.
@ -707,16 +745,11 @@ impl AST {
) -> impl Iterator<Item = (&str, bool, Dynamic)> { ) -> impl Iterator<Item = (&str, bool, Dynamic)> {
self.statements().iter().filter_map(move |stmt| match stmt { self.statements().iter().filter_map(move |stmt| match stmt {
Stmt::Var(expr, name, options, _) Stmt::Var(expr, name, options, _)
if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants if options.contains(AST_OPTION_CONSTANT) && include_constants
|| !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) || !options.contains(AST_OPTION_CONSTANT) && include_variables =>
&& include_variables =>
{ {
if let Some(value) = expr.get_literal_value() { if let Some(value) = expr.get_literal_value() {
Some(( Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value))
name.as_str(),
options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT),
value,
))
} else { } else {
None None
} }
@ -871,6 +904,6 @@ impl AST {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn lib(&self) -> &crate::Module { pub fn lib(&self) -> &crate::Module {
&self.functions &self.lib
} }
} }

View File

@ -165,7 +165,7 @@ impl FnCallHashes {
/// _(internals)_ A function call. /// _(internals)_ A function call.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Default, Hash)] #[derive(Clone, Default, Hash)]
pub struct FnCallExpr { pub struct FnCallExpr {
/// Namespace of the function, if any. /// Namespace of the function, if any.
/// ///
@ -191,6 +191,27 @@ pub struct FnCallExpr {
pub constants: StaticVec<Dynamic>, pub constants: StaticVec<Dynamic>,
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture_parent_scope: bool, pub capture_parent_scope: bool,
/// [Position] of the function name.
pub pos: Position,
}
impl fmt::Debug for FnCallExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ff = f.debug_struct("FnCallExpr");
#[cfg(not(feature = "no_module"))]
self.namespace.as_ref().map(|ns| ff.field("namespace", ns));
ff.field("name", &self.name)
.field("hash", &self.hashes)
.field("arg_exprs", &self.args);
if !self.constants.is_empty() {
ff.field("constant_args", &self.constants);
}
if self.capture_parent_scope {
ff.field("capture_parent_scope", &self.capture_parent_scope);
}
ff.field("pos", &self.pos);
ff.finish()
}
} }
impl FnCallExpr { impl FnCallExpr {
@ -419,7 +440,7 @@ impl Default for Expr {
impl fmt::Debug for Expr { impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut display_pos = self.position(); let mut display_pos = self.start_position();
match self { match self {
Self::DynamicConstant(value, _) => write!(f, "{:?}", value), Self::DynamicConstant(value, _) => write!(f, "{:?}", value),
@ -459,26 +480,12 @@ impl fmt::Debug for Expr {
f.write_str(")") f.write_str(")")
} }
Self::Property(x, _) => write!(f, "Property({})", x.2), Self::Property(x, _) => write!(f, "Property({})", x.2),
Self::Stack(x, _) => write!(f, "StackSlot({})", x), Self::Stack(x, _) => write!(f, "ConstantArg#{}", x),
Self::Stmt(x) => { Self::Stmt(x) => {
f.write_str("ExprStmtBlock")?; f.write_str("ExprStmtBlock")?;
f.debug_list().entries(x.iter()).finish() f.debug_list().entries(x.iter()).finish()
} }
Self::FnCall(x, _) => { Self::FnCall(x, _) => fmt::Debug::fmt(x, f),
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)
.field("args", &x.args);
if !x.constants.is_empty() {
ff.field("constants", &x.constants);
}
if x.capture_parent_scope {
ff.field("capture_parent_scope", &x.capture_parent_scope);
}
ff.finish()
}
Self::Index(x, term, pos) => { Self::Index(x, term, pos) => {
display_pos = *pos; display_pos = *pos;
@ -625,6 +632,7 @@ impl Expr {
args: once(Self::Stack(0, pos)).collect(), args: once(Self::Stack(0, pos)).collect(),
constants: once(f.fn_name().into()).collect(), constants: once(f.fn_name().into()).collect(),
capture_parent_scope: false, capture_parent_scope: false,
pos,
} }
.into(), .into(),
pos, pos,
@ -681,15 +689,30 @@ impl Expr {
| Self::Map(_, pos) | Self::Map(_, pos)
| Self::Variable(_, pos, _) | Self::Variable(_, pos, _)
| Self::Stack(_, pos) | Self::Stack(_, pos)
| Self::FnCall(_, pos) | Self::And(_, pos)
| Self::Or(_, pos)
| Self::Index(_, _, pos) | Self::Index(_, _, pos)
| Self::Dot(_, _, pos)
| Self::Custom(_, pos) | Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) | Self::InterpolatedString(_, pos)
| Self::Property(_, pos) => *pos, | Self::Property(_, pos) => *pos,
Self::Stmt(x) => x.position(), Self::FnCall(x, _) => x.pos,
Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) => x.lhs.position(), Self::Stmt(x) => x.position(),
}
}
/// Get the starting [position][Position] of the expression.
/// For a binary expression, this will be the left-most LHS instead of the operator.
#[inline]
#[must_use]
pub const fn start_position(&self) -> Position {
match self {
Self::And(x, _) | Self::Or(x, _) | Self::Index(x, _, _) | Self::Dot(x, _, _) => {
x.lhs.start_position()
}
Self::FnCall(_, pos) => *pos,
_ => self.position(),
} }
} }
/// Override the [position][Position] of the expression. /// Override the [position][Position] of the expression.
@ -718,7 +741,7 @@ impl Expr {
| Self::InterpolatedString(_, pos) | Self::InterpolatedString(_, pos)
| Self::Property(_, pos) => *pos = new_pos, | Self::Property(_, pos) => *pos = new_pos,
Self::Stmt(x) => x.set_position(new_pos), Self::Stmt(x) => x.set_position(new_pos, Position::NONE),
} }
self self

View File

@ -11,6 +11,9 @@ pub use ast::{ASTNode, AST};
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes}; pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS}; pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
pub use ident::Ident; pub use ident::Ident;
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub use script_fn::EncapsulatedEnviron;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use script_fn::{ScriptFnDef, ScriptFnMetadata}; pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
pub use stmt::{ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, SwitchCases, TryCatchBlock}; pub use stmt::{ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, SwitchCases, TryCatchBlock};

View File

@ -2,24 +2,43 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use super::{FnAccess, StmtBlock}; use super::{FnAccess, StmtBlock};
use crate::{Identifier, Module, Shared, StaticVec}; use crate::{Identifier, StaticVec};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{fmt, hash::Hash}; use std::{fmt, hash::Hash};
/// _(internals)_ Encapsulated AST environment.
/// Exported under the `internals` feature only.
///
/// 1) other functions defined within the same AST
/// 2) the stack of imported [modules][crate::Module]
/// 3) global constants
///
/// Not available under `no_module` or `no_function`.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
#[derive(Debug, Clone)]
pub struct EncapsulatedEnviron {
/// Functions defined within the same [`AST`][crate::AST].
pub lib: crate::Shared<crate::Module>,
/// Imported [modules][crate::Module].
pub imports: Box<[(Identifier, crate::Shared<crate::Module>)]>,
/// Globally-defined constants.
pub constants: Option<crate::eval::GlobalConstants>,
}
/// _(internals)_ A type containing information on a script-defined function. /// _(internals)_ A type containing information on a script-defined function.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ScriptFnDef { pub struct ScriptFnDef {
/// Function body. /// Function body.
pub body: StmtBlock, pub body: StmtBlock,
/// Encapsulated running environment, if any. /// Encapsulated AST environment, if any.
pub lib: Option<Shared<Module>>,
/// Encapsulated stack of imported modules, if any.
/// ///
/// Not available under `no_module`. /// Not available under `no_module` or `no_function`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub global: Option<Box<[(Identifier, Shared<Module>)]>>, #[cfg(not(feature = "no_function"))]
pub environ: Option<EncapsulatedEnviron>,
/// Function name. /// Function name.
pub name: Identifier, pub name: Identifier,
/// Function access mode. /// Function access mode.

View File

@ -134,7 +134,7 @@ pub struct TryCatchBlock {
/// _(internals)_ A scoped block of statements. /// _(internals)_ A scoped block of statements.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Clone, Hash, Default)] #[derive(Clone, Hash, Default)]
pub struct StmtBlock(StaticVec<Stmt>, Position); pub struct StmtBlock(StaticVec<Stmt>, (Position, Position));
impl StmtBlock { impl StmtBlock {
/// A [`StmtBlock`] that does not exist. /// A [`StmtBlock`] that does not exist.
@ -142,16 +142,20 @@ impl StmtBlock {
/// Create a new [`StmtBlock`]. /// Create a new [`StmtBlock`].
#[must_use] #[must_use]
pub fn new(statements: impl IntoIterator<Item = Stmt>, pos: Position) -> Self { pub fn new(
statements: impl IntoIterator<Item = Stmt>,
start_pos: Position,
end_pos: Position,
) -> Self {
let mut statements: StaticVec<_> = statements.into_iter().collect(); let mut statements: StaticVec<_> = statements.into_iter().collect();
statements.shrink_to_fit(); statements.shrink_to_fit();
Self(statements, pos) Self(statements, (start_pos, end_pos))
} }
/// Create an empty [`StmtBlock`]. /// Create an empty [`StmtBlock`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn empty(pos: Position) -> Self { pub const fn empty(pos: Position) -> Self {
Self(StaticVec::new_const(), pos) Self(StaticVec::new_const(), (pos, pos))
} }
/// Is this statements block empty? /// Is this statements block empty?
#[inline(always)] #[inline(always)]
@ -183,16 +187,42 @@ impl StmtBlock {
pub fn iter(&self) -> impl Iterator<Item = &Stmt> { pub fn iter(&self) -> impl Iterator<Item = &Stmt> {
self.0.iter() self.0.iter()
} }
/// Get the position (location of the beginning `{`) of this statements block. /// Get the start position (location of the beginning `{`) of this statements block.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn position(&self) -> Position { pub const fn position(&self) -> Position {
(self.1).0
}
/// Get the end position (location of the ending `}`) of this statements block.
#[inline(always)]
#[must_use]
pub const fn end_position(&self) -> Position {
(self.1).1
}
/// Get the positions (locations of the beginning `{` and ending `}`) of this statements block.
#[inline(always)]
#[must_use]
pub const fn positions(&self) -> (Position, Position) {
self.1 self.1
} }
/// Set the position (location of the beginning `{`) of this statements block. /// Get the positions (locations of the beginning `{` and ending `}`) of this statements block
/// or a default.
#[inline(always)] #[inline(always)]
pub fn set_position(&mut self, pos: Position) { #[must_use]
self.1 = pos; pub const fn positions_or_else(
&self,
def_start_pos: Position,
def_end_pos: Position,
) -> (Position, Position) {
(
(self.1).0.or_else(def_start_pos),
(self.1).1.or_else(def_end_pos),
)
}
/// Set the positions of this statements block.
#[inline(always)]
pub fn set_position(&mut self, start_pos: Position, end_pos: Position) {
self.1 = (start_pos, end_pos);
} }
} }
@ -230,7 +260,12 @@ impl fmt::Debug for StmtBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Block")?; f.write_str("Block")?;
fmt::Debug::fmt(&self.0, f)?; fmt::Debug::fmt(&self.0, f)?;
self.1.debug_print(f) (self.1).0.debug_print(f)?;
#[cfg(not(feature = "no_position"))]
if !(self.1).1.is_none() {
write!(f, "-{:?}", (self.1).1)?;
}
Ok(())
} }
} }
@ -239,10 +274,10 @@ impl From<Stmt> for StmtBlock {
fn from(stmt: Stmt) -> Self { fn from(stmt: Stmt) -> Self {
match stmt { match stmt {
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos), Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
Stmt::Noop(pos) => Self(StaticVec::new_const(), pos), Stmt::Noop(pos) => Self(StaticVec::new_const(), (pos, pos)),
_ => { _ => {
let pos = stmt.position(); let pos = stmt.position();
Self(vec![stmt].into(), pos) Self(vec![stmt].into(), (pos, Position::NONE))
} }
} }
} }
@ -309,7 +344,7 @@ pub enum Stmt {
/// function call forming one statement. /// function call forming one statement.
FnCall(Box<FnCallExpr>, Position), FnCall(Box<FnCallExpr>, Position),
/// `{` stmt`;` ... `}` /// `{` stmt`;` ... `}`
Block(Box<[Stmt]>, Position), Block(Box<[Stmt]>, (Position, Position)),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
TryCatch(Box<TryCatchBlock>, Position), TryCatch(Box<TryCatchBlock>, Position),
/// [expression][Expr] /// [expression][Expr]
@ -377,7 +412,7 @@ impl Stmt {
match self { match self {
Self::Noop(pos) Self::Noop(pos)
| Self::BreakLoop(_, pos) | Self::BreakLoop(_, pos)
| Self::Block(_, pos) | Self::Block(_, (pos, _))
| Self::Assignment(_, pos) | Self::Assignment(_, pos)
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
| Self::If(_, _, pos) | Self::If(_, _, pos)
@ -389,7 +424,7 @@ impl Stmt {
| Self::Var(_, _, _, pos) | Self::Var(_, _, _, pos)
| Self::TryCatch(_, pos) => *pos, | Self::TryCatch(_, pos) => *pos,
Self::Expr(x) => x.position(), Self::Expr(x) => x.start_position(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Import(_, _, pos) => *pos, Self::Import(_, _, pos) => *pos,
@ -405,7 +440,7 @@ impl Stmt {
match self { match self {
Self::Noop(pos) Self::Noop(pos)
| Self::BreakLoop(_, pos) | Self::BreakLoop(_, pos)
| Self::Block(_, pos) | Self::Block(_, (pos, _))
| Self::Assignment(_, pos) | Self::Assignment(_, pos)
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
| Self::If(_, _, pos) | Self::If(_, _, pos)

View File

@ -9,32 +9,29 @@ Tools for running Rhai scripts.
| [`rhai-repl`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-repl.rs) | `rustyline` | simple REPL that interactively evaluates statements | | [`rhai-repl`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-repl.rs) | `rustyline` | simple REPL that interactively evaluates statements |
| [`rhai-dbg`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-dbg.rs) | `debugging` | the _Rhai Debugger_ | | [`rhai-dbg`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-dbg.rs) | `debugging` | the _Rhai Debugger_ |
There is a feature called `bin-features` which automatically includes all the necessary features
required for building these tools.
How to Run How to Run
---------- ----------
```sh ```sh
cargo run --bin sample_app_to_run cargo run --features bin-features --bin sample_app_to_run
```
or with required features
```sh
cargo run --bin sample_app_to_run --features feature1,feature2,feature3
``` ```
How to Install How to Install
-------------- --------------
To install these all tools (with [`decimal`] and [`metadata`] support), use the following command: To install these all tools (with full features), use the following command:
```sh ```sh
cargo install --path . --bins --features decimal,metadata,debugging,rustyline cargo install --path . --bins --features bin-features
``` ```
or specifically: or specifically:
```sh ```sh
cargo install --path . --bin rhai-run --features decimal,metadata,debugging,rustyline cargo install --path . --bin rhai-run --features bin-features
``` ```

View File

@ -1,4 +1,4 @@
use rhai::debugger::DebuggerCommand; use rhai::debugger::{BreakPoint, DebuggerCommand, DebuggerEvent};
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope}; use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
use std::{ use std::{
@ -36,6 +36,32 @@ fn print_source(lines: &[String], pos: Position, offset: usize) {
} }
} }
fn print_current_source(
context: &mut rhai::EvalContext,
source: Option<&str>,
pos: Position,
lines: &Vec<String>,
) {
let current_source = &mut *context
.global_runtime_state_mut()
.debugger
.state_mut()
.write_lock::<ImmutableString>()
.unwrap();
let src = source.unwrap_or("");
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);
}
}
/// Pretty-print error. /// Pretty-print error.
fn print_error(input: &str, mut err: EvalAltResult) { fn print_error(input: &str, mut err: EvalAltResult) {
let lines: Vec<_> = input.trim().split('\n').collect(); let lines: Vec<_> = input.trim().split('\n').collect();
@ -71,36 +97,38 @@ fn print_error(input: &str, mut err: EvalAltResult) {
/// Print debug help. /// Print debug help.
fn print_debug_help() { fn print_debug_help() {
println!("help => print this help"); println!("help, h => print this help");
println!("quit, exit, kill => quit"); println!("quit, q, exit, kill => quit");
println!("scope => print the scope"); println!("scope => print the scope");
println!("print => print all variables de-duplicated"); println!("print, p => print all variables de-duplicated");
println!("print <variable> => print the current value of a variable"); println!("print/p <variable> => print the current value of a variable");
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
println!("imports => print all imported modules"); println!("imports => print all imported modules");
println!("node => print the current AST node"); println!("node => print the current AST node");
println!("backtrace => print the current call-stack"); println!("list, l => print the current source line");
println!("breakpoints => print all break-points"); println!("backtrace, bt => print the current call-stack");
println!("enable <bp#> => enable a break-point"); println!("info break, i b => print all break-points");
println!("disable <bp#> => disable a break-point"); println!("enable/en <bp#> => enable a break-point");
println!("delete <bp#> => delete a break-point"); println!("disable/dis <bp#> => disable a break-point");
println!("clear => delete all break-points"); println!("delete, d => delete all break-points");
println!("delete/d <bp#> => delete a break-point");
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
println!("break => set a new break-point at the current position"); println!("break, b => set a new break-point at the current position");
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
println!("break <line#> => set a new break-point at a line number"); println!("break/b <line#> => set a new break-point at a line number");
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
println!("break .<prop> => set a new break-point for a property access"); println!("break/b .<prop> => set a new break-point for a property access");
println!("break <func> => set a new break-point for a function call"); println!("break/b <func> => set a new break-point for a function call");
println!( println!(
"break <func> <#args> => set a new break-point for a function call with #args arguments" "break/b <func> <#args> => set a new break-point for a function call with #args arguments"
); );
println!("throw [message] => throw an exception (message optional)"); println!("throw [message] => throw an exception (message optional)");
println!("run => restart the script evaluation from beginning"); println!("run, r => restart the script evaluation from beginning");
println!("step => go to the next expression, diving into functions"); println!("step, s => go to the next expression, diving into functions");
println!("over => go to the next expression, skipping oer functions"); println!("over => go to the next expression, skipping oer functions");
println!("next => go to the next statement, skipping over functions"); println!("next, n, <Enter> => go to the next statement, skipping over functions");
println!("continue => continue normal execution"); println!("finish, f => continue until the end of the current function call");
println!("continue, c => continue normal execution");
println!(); println!();
} }
@ -220,36 +248,58 @@ fn main() {
// Hook up debugger // Hook up debugger
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect(); let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
engine.on_debugger( engine.register_debugger(
// Store the current source in the debugger state // Store the current source in the debugger state
|| "".into(), || "".into(),
// Main debugging interface // Main debugging interface
move |context, node, source, pos| { move |context, event, node, source, pos| {
{ match event {
let current_source = &mut *context DebuggerEvent::Step => (),
.global_runtime_state_mut() DebuggerEvent::BreakPoint(n) => {
.debugger match context.global_runtime_state().debugger.break_points()[n] {
.state_mut() #[cfg(not(feature = "no_position"))]
.write_lock::<ImmutableString>() BreakPoint::AtPosition { .. } => (),
.unwrap(); BreakPoint::AtFunctionName { ref name, .. }
| BreakPoint::AtFunctionCall { ref name, .. } => {
let src = source.unwrap_or(""); println!("! Call to function {}.", name)
}
// Check source #[cfg(not(feature = "no_object"))]
if src != current_source { BreakPoint::AtProperty { ref name, .. } => {
println!(">>> Source => {}", source.unwrap_or("main script")); println!("! Property {} accessed.", name)
*current_source = src.into(); }
}
} }
DebuggerEvent::FunctionExitWithValue(r) => {
if !src.is_empty() { println!(
// Print just a line number for imported modules "! Return from function call '{}' => {}",
println!("{} @ {:?}", src, pos); context
} else { .global_runtime_state()
// Print the current source line .debugger
print_source(&lines, pos, 0); .call_stack()
.last()
.unwrap()
.fn_name,
r
)
}
DebuggerEvent::FunctionExitWithError(err) => {
println!(
"! Return from function call '{}' with error: {}",
context
.global_runtime_state()
.debugger
.call_stack()
.last()
.unwrap()
.fn_name,
err
)
} }
} }
// Print current source line
print_current_source(context, source, pos, &lines);
// Read stdin for commands // Read stdin for commands
let mut input = String::new(); let mut input = String::new();
@ -267,8 +317,8 @@ fn main() {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.as_slice() .as_slice()
{ {
["help", ..] => print_debug_help(), ["help" | "h", ..] => print_debug_help(),
["exit", ..] | ["quit", ..] | ["kill", ..] => { ["exit" | "quit" | "q" | "kill", ..] => {
println!("Script terminated. Bye!"); println!("Script terminated. Bye!");
exit(0); exit(0);
} }
@ -276,12 +326,14 @@ fn main() {
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos); println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
println!(); println!();
} }
["continue", ..] => break Ok(DebuggerCommand::Continue), ["list" | "l", ..] => print_current_source(context, source, pos, &lines),
[] | ["step", ..] => break Ok(DebuggerCommand::StepInto), ["continue" | "c", ..] => break Ok(DebuggerCommand::Continue),
["finish" | "f", ..] => break Ok(DebuggerCommand::FunctionExit),
[] | ["step" | "s", ..] => break Ok(DebuggerCommand::StepInto),
["over", ..] => break Ok(DebuggerCommand::StepOver), ["over", ..] => break Ok(DebuggerCommand::StepOver),
["next", ..] => break Ok(DebuggerCommand::Next), ["next" | "n", ..] => break Ok(DebuggerCommand::Next),
["scope", ..] => print_scope(context.scope(), false), ["scope", ..] => print_scope(context.scope(), false),
["print", var_name, ..] => { ["print" | "p", var_name, ..] => {
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) { if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
if value.is::<()>() { if value.is::<()>() {
println!("=> ()"); println!("=> ()");
@ -292,7 +344,7 @@ fn main() {
eprintln!("Variable not found: {}", var_name); eprintln!("Variable not found: {}", var_name);
} }
} }
["print", ..] => print_scope(context.scope(), true), ["print" | "p"] => print_scope(context.scope(), true),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
["imports", ..] => { ["imports", ..] => {
for (i, (name, module)) in context for (i, (name, module)) in context
@ -311,7 +363,7 @@ fn main() {
println!(); println!();
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
["backtrace", ..] => { ["backtrace" | "bt", ..] => {
for frame in context for frame in context
.global_runtime_state() .global_runtime_state()
.debugger .debugger
@ -322,15 +374,7 @@ fn main() {
println!("{}", frame) println!("{}", frame)
} }
} }
["clear", ..] => { ["info", "break", ..] | ["i", "b", ..] => Iterator::for_each(
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.clear();
println!("All break-points cleared.");
}
["breakpoints", ..] => Iterator::for_each(
context context
.global_runtime_state() .global_runtime_state()
.debugger .debugger
@ -347,7 +391,7 @@ fn main() {
_ => println!("[{}] {}", i + 1, bp), _ => println!("[{}] {}", i + 1, bp),
}, },
), ),
["enable", n, ..] => { ["enable" | "en", n, ..] => {
if let Ok(n) = n.parse::<usize>() { if let Ok(n) = n.parse::<usize>() {
let range = 1..=context let range = 1..=context
.global_runtime_state_mut() .global_runtime_state_mut()
@ -370,7 +414,7 @@ fn main() {
eprintln!("Invalid break-point: '{}'", n); eprintln!("Invalid break-point: '{}'", n);
} }
} }
["disable", n, ..] => { ["disable" | "dis", n, ..] => {
if let Ok(n) = n.parse::<usize>() { if let Ok(n) = n.parse::<usize>() {
let range = 1..=context let range = 1..=context
.global_runtime_state_mut() .global_runtime_state_mut()
@ -393,7 +437,7 @@ fn main() {
eprintln!("Invalid break-point: '{}'", n); eprintln!("Invalid break-point: '{}'", n);
} }
} }
["delete", n, ..] => { ["delete" | "d", n, ..] => {
if let Ok(n) = n.parse::<usize>() { if let Ok(n) = n.parse::<usize>() {
let range = 1..=context let range = 1..=context
.global_runtime_state_mut() .global_runtime_state_mut()
@ -414,7 +458,15 @@ fn main() {
eprintln!("Invalid break-point: '{}'", n); eprintln!("Invalid break-point: '{}'", n);
} }
} }
["break", fn_name, args, ..] => { ["delete" | "d", ..] => {
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.clear();
println!("All break-points deleted.");
}
["break" | "b", fn_name, args, ..] => {
if let Ok(args) = args.parse::<usize>() { if let Ok(args) = args.parse::<usize>() {
let bp = rhai::debugger::BreakPoint::AtFunctionCall { let bp = rhai::debugger::BreakPoint::AtFunctionCall {
name: fn_name.trim().into(), name: fn_name.trim().into(),
@ -433,7 +485,7 @@ fn main() {
} }
// Property name // Property name
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
["break", param] if param.starts_with('.') && param.len() > 1 => { ["break" | "b", param] if param.starts_with('.') && param.len() > 1 => {
let bp = rhai::debugger::BreakPoint::AtProperty { let bp = rhai::debugger::BreakPoint::AtProperty {
name: param[1..].into(), name: param[1..].into(),
enabled: true, enabled: true,
@ -447,7 +499,7 @@ fn main() {
} }
// Numeric parameter // Numeric parameter
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
["break", param] if param.parse::<usize>().is_ok() => { ["break" | "b", param] if param.parse::<usize>().is_ok() => {
let n = param.parse::<usize>().unwrap(); let n = param.parse::<usize>().unwrap();
let range = if source.is_none() { let range = if source.is_none() {
1..=lines.len() 1..=lines.len()
@ -472,7 +524,7 @@ fn main() {
} }
} }
// Function name parameter // Function name parameter
["break", param] => { ["break" | "b", param] => {
let bp = rhai::debugger::BreakPoint::AtFunctionName { let bp = rhai::debugger::BreakPoint::AtFunctionName {
name: param.trim().into(), name: param.trim().into(),
enabled: true, enabled: true,
@ -485,7 +537,7 @@ fn main() {
.push(bp); .push(bp);
} }
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
["break", ..] => { ["break" | "b"] => {
let bp = rhai::debugger::BreakPoint::AtPosition { let bp = rhai::debugger::BreakPoint::AtPosition {
source: source.unwrap_or("").into(), source: source.unwrap_or("").into(),
pos, pos,
@ -505,7 +557,7 @@ fn main() {
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or(""); let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into()); break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
} }
["run", ..] => { ["run" | "r", ..] => {
println!("Restarting script..."); println!("Restarting script...");
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into()); break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
} }

View File

@ -44,9 +44,13 @@ fn print_error(input: &str, mut err: EvalAltResult) {
/// Print help text. /// Print help text.
fn print_help() { fn print_help() {
println!("help => print this help"); println!("help => print this help");
println!("keys => print list of key bindings");
println!("quit, exit => quit"); println!("quit, exit => quit");
println!("keys => print list of key bindings");
println!("history => print lines history"); println!("history => print lines history");
println!("!! => repeat the last history line");
println!("!<#> => repeat a particular history line");
println!("!<text> => repeat the last history line starting with some text");
println!("!?<text> => repeat the last history line containing some text");
println!("scope => print all variables in the scope"); println!("scope => print all variables in the scope");
println!("strict => toggle on/off Strict Variables Mode"); println!("strict => toggle on/off Strict Variables Mode");
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
@ -295,6 +299,7 @@ fn main() {
engine.set_module_resolver(resolver); engine.set_module_resolver(resolver);
} }
// Register sample functions
engine engine
.register_fn("test", |x: INT, y: INT| format!("{} {}", x, y)) .register_fn("test", |x: INT, y: INT| format!("{} {}", x, y))
.register_fn("test", |x: &mut INT, y: INT, z: &str| { .register_fn("test", |x: &mut INT, y: INT, z: &str| {
@ -310,6 +315,10 @@ fn main() {
// REPL loop // REPL loop
let mut input = String::new(); let mut input = String::new();
let mut replacement = None;
let mut replacement_index = 0;
let mut history_offset = 1;
let mut main_ast = AST::empty(); let mut main_ast = AST::empty();
let mut ast_u = AST::empty(); let mut ast_u = AST::empty();
let mut ast = AST::empty(); let mut ast = AST::empty();
@ -317,35 +326,53 @@ fn main() {
print_help(); print_help();
'main_loop: loop { 'main_loop: loop {
input.clear(); if let Some(replace) = replacement.take() {
input = replace;
loop { if rl.add_history_entry(input.clone()) {
let prompt = if input.is_empty() { history_offset += 1;
"rhai-repl> " }
if input.contains('\n') {
println!("[{}] ~~~~", replacement_index);
println!("{}", input);
println!("~~~~");
} else { } else {
" > " println!("[{}] {}", replacement_index, input);
}; }
replacement_index = 0;
} else {
input.clear();
match rl.readline(prompt) { loop {
// Line continuation let prompt = if input.is_empty() {
Ok(mut line) if line.ends_with("\\") => { "rhai-repl> "
line.pop(); } else {
input += line.trim_end(); " > "
input.push('\n'); };
}
Ok(line) => { match rl.readline(prompt) {
input += line.trim_end(); // Line continuation
if !input.is_empty() { Ok(mut line) if line.ends_with("\\") => {
rl.add_history_entry(input.clone()); line.pop();
input += line.trim_end();
input.push('\n');
}
Ok(line) => {
input += line.trim_end();
if !input.is_empty() && !input.starts_with('!') && input.trim() != "history"
{
if rl.add_history_entry(input.clone()) {
history_offset += 1;
}
}
break;
} }
break;
}
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'main_loop, Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'main_loop,
Err(err) => { Err(err) => {
eprintln!("Error: {:?}", err); eprintln!("Error: {:?}", err);
break 'main_loop; break 'main_loop;
}
} }
} }
} }
@ -370,10 +397,10 @@ fn main() {
"history" => { "history" => {
for (i, h) in rl.history().iter().enumerate() { for (i, h) in rl.history().iter().enumerate() {
match &h.split('\n').collect::<Vec<_>>()[..] { match &h.split('\n').collect::<Vec<_>>()[..] {
[line] => println!("[{}] {}", i + 1, line), [line] => println!("[{}] {}", history_offset + i, line),
lines => { lines => {
for (x, line) in lines.iter().enumerate() { for (x, line) in lines.iter().enumerate() {
let number = format!("[{}]", i + 1); let number = format!("[{}]", history_offset + i);
if x == 0 { if x == 0 {
println!("{} {}", number, line.trim_end()); println!("{} {}", number, line.trim_end());
} else { } else {
@ -446,6 +473,57 @@ fn main() {
); );
continue; continue;
} }
"!!" => {
if let Some(line) = rl.history().last() {
replacement = Some(line.clone());
replacement_index = history_offset + rl.history().len() - 1;
} else {
eprintln!("No lines history!");
}
continue;
}
_ if script.starts_with("!?") => {
let text = script[2..].trim();
if let Some((n, line)) = rl
.history()
.iter()
.rev()
.enumerate()
.find(|&(_, h)| h.contains(text))
{
replacement = Some(line.clone());
replacement_index = history_offset + (rl.history().len() - 1 - n);
} else {
eprintln!("History line not found: {}", text);
}
continue;
}
_ if script.starts_with('!') => {
if let Ok(num) = script[1..].parse::<usize>() {
if num >= history_offset {
if let Some(line) = rl.history().get(num - history_offset) {
replacement = Some(line.clone());
replacement_index = num;
continue;
}
}
} else {
let prefix = script[1..].trim();
if let Some((n, line)) = rl
.history()
.iter()
.rev()
.enumerate()
.find(|&(_, h)| h.trim_start().starts_with(prefix))
{
replacement = Some(line.clone());
replacement_index = history_offset + (rl.history().len() - 1 - n);
continue;
}
}
eprintln!("History line not found: {}", &script[1..]);
continue;
}
_ => (), _ => (),
} }

View File

@ -1,18 +1,18 @@
//! Main module defining the script evaluation [`Engine`]. //! Main module defining the script evaluation [`Engine`].
use crate::api::custom_syntax::CustomSyntax; use crate::api::custom_syntax::CustomSyntax;
use crate::func::native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback}; use crate::func::native::{
OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback,
};
use crate::packages::{Package, StandardPackage}; use crate::packages::{Package, StandardPackage};
use crate::tokenizer::Token; use crate::tokenizer::Token;
use crate::types::dynamic::{map_std_type_name, Union}; use crate::types::dynamic::Union;
use crate::{ use crate::{
Dynamic, Identifier, ImmutableString, Module, Position, RhaiError, RhaiResult, Shared, Dynamic, Identifier, ImmutableString, Module, Position, RhaiResult, Shared, StaticVec,
StaticVec, ERR,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
any::type_name,
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
fmt, fmt,
num::NonZeroU8, num::NonZeroU8,
@ -115,6 +115,8 @@ pub struct Engine {
pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>, pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
/// Custom syntax. /// Custom syntax.
pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>, pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
/// Callback closure for filtering variable definition.
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,
/// Callback closure for resolving variable access. /// Callback closure for resolving variable access.
pub(crate) resolve_var: Option<Box<OnVarCallback>>, pub(crate) resolve_var: Option<Box<OnVarCallback>>,
/// Callback closure to remap tokens during parsing. /// Callback closure to remap tokens during parsing.
@ -162,6 +164,7 @@ impl fmt::Debug for Engine {
.field("disabled_symbols", &self.disabled_symbols) .field("disabled_symbols", &self.disabled_symbols)
.field("custom_keywords", &self.custom_keywords) .field("custom_keywords", &self.custom_keywords)
.field("custom_syntax", &(!self.custom_syntax.is_empty())) .field("custom_syntax", &(!self.custom_syntax.is_empty()))
.field("def_var_filter", &self.def_var_filter.is_some())
.field("resolve_var", &self.resolve_var.is_some()) .field("resolve_var", &self.resolve_var.is_some())
.field("token_mapper", &self.token_mapper.is_some()) .field("token_mapper", &self.token_mapper.is_some())
.field("print", &self.print.is_some()) .field("print", &self.print.is_some())
@ -274,6 +277,7 @@ impl Engine {
custom_keywords: BTreeMap::new(), custom_keywords: BTreeMap::new(),
custom_syntax: BTreeMap::new(), custom_syntax: BTreeMap::new(),
def_var_filter: None,
resolve_var: None, resolve_var: None,
token_mapper: None, token_mapper: None,
@ -337,59 +341,4 @@ impl Engine {
result result
} }
/// Pretty-print a type name.
///
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
/// the type name provided for the registration will be used.
///
/// # Panics
///
/// Panics if the type name is `&mut`.
#[inline]
#[must_use]
pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names
.get(name)
.map(|s| s.as_str())
.unwrap_or_else(|| map_std_type_name(name, true))
}
/// Format a type name.
///
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
/// the type name provided for the registration will be used.
#[cfg(feature = "metadata")]
#[inline]
#[must_use]
pub(crate) fn format_type_name<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> {
if name.starts_with("&mut ") {
let x = &name[5..];
let r = self.format_type_name(x);
return if x != r {
format!("&mut {}", r).into()
} else {
name.into()
};
}
self.type_names
.get(name)
.map(|s| s.as_str())
.unwrap_or_else(|| match name {
"INT" => return type_name::<crate::INT>(),
#[cfg(not(feature = "no_float"))]
"FLOAT" => return type_name::<crate::FLOAT>(),
_ => map_std_type_name(name, false),
})
.into()
}
/// Make a `Box<`[`EvalAltResult<ErrorMismatchDataType>`][ERR::ErrorMismatchDataType]`>`.
#[inline]
#[must_use]
pub(crate) fn make_type_mismatch_err<T>(&self, typ: &str, pos: Position) -> RhaiError {
ERR::ErrorMismatchDataType(self.map_type_name(type_name::<T>()).into(), typ.into(), pos)
.into()
}
} }

View File

@ -146,7 +146,7 @@ impl Engine {
match chain_type { match chain_type {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ChainType::Indexing => { ChainType::Indexing => {
let pos = rhs.position(); let pos = rhs.start_position();
let root_pos = idx_val.position(); let root_pos = idx_val.position();
let idx_val = idx_val.into_index_value().expect("`ChainType::Index`"); let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
@ -159,7 +159,7 @@ impl Engine {
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
let mut idx_val_for_setter = idx_val.clone(); let mut idx_val_for_setter = idx_val.clone();
let idx_pos = x.lhs.position(); let idx_pos = x.lhs.start_position();
let rhs_chain = rhs.into(); let rhs_chain = rhs.into();
let (try_setter, result) = { let (try_setter, result) = {
@ -189,8 +189,8 @@ impl Engine {
let fn_name = crate::engine::FN_IDX_SET; let fn_name = crate::engine::FN_IDX_SET;
if let Err(err) = self.exec_fn_call( if let Err(err) = self.exec_fn_call(
global, state, lib, fn_name, hash_set, args, is_ref_mut, true, None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
root_pos, None, level, true, root_pos, level,
) { ) {
// Just ignore if there is no index setter // Just ignore if there is no index setter
if !matches!(*err, ERR::ErrorFunctionNotFound(_, _)) { if !matches!(*err, ERR::ErrorFunctionNotFound(_, _)) {
@ -216,6 +216,7 @@ impl Engine {
Ok(ref mut obj_ptr) => { Ok(ref mut obj_ptr) => {
self.eval_op_assignment( self.eval_op_assignment(
global, state, lib, op_info, op_pos, obj_ptr, root, new_val, global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
level,
) )
.map_err(|err| err.fill_position(new_pos))?; .map_err(|err| err.fill_position(new_pos))?;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -239,8 +240,8 @@ impl Engine {
let fn_name = crate::engine::FN_IDX_SET; let fn_name = crate::engine::FN_IDX_SET;
self.exec_fn_call( self.exec_fn_call(
global, state, lib, fn_name, hash_set, args, is_ref_mut, true, None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
root_pos, None, level, true, root_pos, level,
)?; )?;
} }
@ -302,6 +303,7 @@ impl Engine {
)?; )?;
self.eval_op_assignment( self.eval_op_assignment(
global, state, lib, op_info, op_pos, val_target, root, new_val, global, state, lib, op_info, op_pos, val_target, root, new_val,
level,
) )
.map_err(|err| err.fill_position(new_pos))?; .map_err(|err| err.fill_position(new_pos))?;
} }
@ -333,8 +335,8 @@ impl Engine {
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
let (mut orig_val, _) = self let (mut orig_val, _) = self
.exec_fn_call( .exec_fn_call(
global, state, lib, getter, hash, args, is_ref_mut, true, *pos, None, global, state, lib, getter, hash, args, is_ref_mut, true,
None, level, *pos, level,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
@ -364,6 +366,7 @@ impl Engine {
&mut (&mut orig_val).into(), &mut (&mut orig_val).into(),
root, root,
new_val, new_val,
level,
) )
.map_err(|err| err.fill_position(new_pos))?; .map_err(|err| err.fill_position(new_pos))?;
@ -373,7 +376,7 @@ impl Engine {
let hash = crate::ast::FnCallHashes::from_native(*hash_set); let hash = crate::ast::FnCallHashes::from_native(*hash_set);
let args = &mut [target.as_mut(), &mut new_val]; let args = &mut [target.as_mut(), &mut new_val];
self.exec_fn_call( self.exec_fn_call(
global, state, lib, setter, hash, args, is_ref_mut, true, *pos, None, None, global, state, lib, setter, hash, args, is_ref_mut, true, *pos,
level, level,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
@ -386,8 +389,8 @@ impl Engine {
let pos = Position::NONE; let pos = Position::NONE;
self.exec_fn_call( self.exec_fn_call(
global, state, lib, fn_name, hash_set, args, is_ref_mut, true, None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
pos, None, level, true, pos, level,
) )
.map_err( .map_err(
|idx_err| match *idx_err { |idx_err| match *idx_err {
@ -408,7 +411,7 @@ impl Engine {
let hash = crate::ast::FnCallHashes::from_native(*hash_get); let hash = crate::ast::FnCallHashes::from_native(*hash_get);
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
self.exec_fn_call( self.exec_fn_call(
global, state, lib, getter, hash, args, is_ref_mut, true, *pos, None, None, global, state, lib, getter, hash, args, is_ref_mut, true, *pos,
level, level,
) )
.map_or_else( .map_or_else(
@ -508,8 +511,8 @@ impl Engine {
// Assume getters are always pure // Assume getters are always pure
let (mut val, _) = self let (mut val, _) = self
.exec_fn_call( .exec_fn_call(
global, state, lib, getter, hash_get, args, is_ref_mut, None, global, state, lib, getter, hash_get, args,
true, pos, None, level, is_ref_mut, true, pos, level,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
@ -556,8 +559,8 @@ impl Engine {
let mut arg_values = [target.as_mut(), val]; let mut arg_values = [target.as_mut(), val];
let args = &mut arg_values; let args = &mut arg_values;
self.exec_fn_call( self.exec_fn_call(
global, state, lib, setter, hash_set, args, is_ref_mut, None, global, state, lib, setter, hash_set, args,
true, pos, None, level, is_ref_mut, true, pos, level,
) )
.or_else( .or_else(
|err| match *err { |err| match *err {
@ -571,8 +574,8 @@ impl Engine {
global.hash_idx_set(), global.hash_idx_set(),
); );
self.exec_fn_call( self.exec_fn_call(
global, state, lib, fn_name, hash_set, args, None, global, state, lib, fn_name, hash_set,
is_ref_mut, true, pos, None, level, args, is_ref_mut, true, pos, level,
) )
.or_else(|idx_err| match *idx_err { .or_else(|idx_err| match *idx_err {
ERR::ErrorIndexingType(_, _) => { ERR::ErrorIndexingType(_, _) => {
@ -626,7 +629,7 @@ impl Engine {
} }
} }
// Syntax error // Syntax error
_ => Err(ERR::ErrorDotExpr("".into(), rhs.position()).into()), _ => Err(ERR::ErrorDotExpr("".into(), rhs.start_position()).into()),
} }
} }
} }
@ -670,7 +673,7 @@ impl Engine {
self.inc_operations(&mut global.num_operations, *var_pos)?; self.inc_operations(&mut global.num_operations, *var_pos)?;
let (mut target, _) = let (mut target, _) =
self.search_namespace(scope, global, state, lib, this_ptr, lhs)?; self.search_namespace(scope, global, state, lib, this_ptr, lhs, level)?;
let obj_ptr = &mut target; let obj_ptr = &mut target;
let root = (x.2.as_str(), *var_pos); let root = (x.2.as_str(), *var_pos);
@ -688,7 +691,7 @@ impl Engine {
expr => { expr => {
let value = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?; let value = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?;
let obj_ptr = &mut value.into(); let obj_ptr = &mut value.into();
let root = ("", expr.position()); let root = ("", expr.start_position());
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values, global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
chain_type, level, new_val, chain_type, level, new_val,
@ -732,7 +735,7 @@ impl Engine {
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE), (crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|(mut values, mut pos), expr| -> RhaiResultOf<_> { |(mut values, mut pos), expr| -> RhaiResultOf<_> {
let (value, arg_pos) = self.get_arg_value( let (value, arg_pos) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants, scope, global, state, lib, this_ptr, expr, constants, level,
)?; )?;
if values.is_empty() { if values.is_empty() {
pos = arg_pos; pos = arg_pos;
@ -778,7 +781,7 @@ impl Engine {
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE), (crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|(mut values, mut pos), expr| -> RhaiResultOf<_> { |(mut values, mut pos), expr| -> RhaiResultOf<_> {
let (value, arg_pos) = self.get_arg_value( let (value, arg_pos) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants, scope, global, state, lib, this_ptr, expr, constants, level,
)?; )?;
if values.is_empty() { if values.is_empty() {
pos = arg_pos pos = arg_pos
@ -801,7 +804,10 @@ impl Engine {
_ if _parent_chain_type == ChainType::Indexing => self _ if _parent_chain_type == ChainType::Indexing => self
.eval_expr(scope, global, state, lib, this_ptr, lhs, level) .eval_expr(scope, global, state, lib, this_ptr, lhs, level)
.map(|v| { .map(|v| {
super::ChainArgument::from_index_value(v.flatten(), lhs.position()) super::ChainArgument::from_index_value(
v.flatten(),
lhs.start_position(),
)
})?, })?,
expr => unreachable!("unknown chained expression: {:?}", expr), expr => unreachable!("unknown chained expression: {:?}", expr),
}; };
@ -825,7 +831,7 @@ impl Engine {
_ if _parent_chain_type == ChainType::Indexing => idx_values.push( _ if _parent_chain_type == ChainType::Indexing => idx_values.push(
self.eval_expr(scope, global, state, lib, this_ptr, expr, level) self.eval_expr(scope, global, state, lib, this_ptr, expr, level)
.map(|v| { .map(|v| {
super::ChainArgument::from_index_value(v.flatten(), expr.position()) super::ChainArgument::from_index_value(v.flatten(), expr.start_position())
})?, })?,
), ),
_ => unreachable!("unknown chained expression: {:?}", expr), _ => unreachable!("unknown chained expression: {:?}", expr),
@ -1044,7 +1050,7 @@ impl Engine {
let pos = Position::NONE; let pos = Position::NONE;
self.exec_fn_call( self.exec_fn_call(
global, state, lib, fn_name, hash_get, args, true, true, pos, None, level, None, global, state, lib, fn_name, hash_get, args, true, true, pos, level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
} }

View File

@ -3,10 +3,10 @@
use super::{EvalContext, EvalState, GlobalRuntimeState}; use super::{EvalContext, EvalState, GlobalRuntimeState};
use crate::ast::{ASTNode, Expr, Stmt}; use crate::ast::{ASTNode, Expr, Stmt};
use crate::{Dynamic, Engine, Identifier, Module, Position, RhaiResultOf, Scope}; use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope};
use std::fmt;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{fmt, mem};
/// Callback function to initialize the debugger. /// Callback function to initialize the debugger.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -17,11 +17,22 @@ pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
/// Callback function for debugging. /// Callback function for debugging.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnDebuggerCallback = pub type OnDebuggerCallback = dyn Fn(
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>; &mut EvalContext,
DebuggerEvent,
ASTNode,
Option<&str>,
Position,
) -> RhaiResultOf<DebuggerCommand>;
/// Callback function for debugging. /// Callback function for debugging.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand> pub type OnDebuggerCallback = dyn Fn(
&mut EvalContext,
DebuggerEvent,
ASTNode,
Option<&str>,
Position,
) -> RhaiResultOf<DebuggerCommand>
+ Send + Send
+ Sync; + Sync;
@ -36,6 +47,50 @@ pub enum DebuggerCommand {
StepOver, StepOver,
// Run to the next statement, skipping over functions. // Run to the next statement, skipping over functions.
Next, Next,
// Run to the end of the current function call.
FunctionExit,
}
impl Default for DebuggerCommand {
#[inline(always)]
fn default() -> Self {
Self::Continue
}
}
/// The debugger status.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum DebuggerStatus {
// Stop at the next statement or expression.
Next(bool, bool),
// Run to the end of the current level of function call.
FunctionExit(usize),
}
impl Default for DebuggerStatus {
#[inline(always)]
fn default() -> Self {
Self::CONTINUE
}
}
impl DebuggerStatus {
pub const CONTINUE: Self = Self::Next(false, false);
pub const STEP: Self = Self::Next(true, true);
pub const NEXT: Self = Self::Next(true, false);
}
/// A event that triggers the debugger.
#[derive(Debug, Clone, Copy)]
pub enum DebuggerEvent<'a> {
// Break on next step.
Step,
// Break on break-point.
BreakPoint(usize),
// Return from a function with a value.
FunctionExitWithValue(&'a Dynamic),
// Return from a function with a value.
FunctionExitWithError(&'a EvalAltResult),
} }
/// A break-point for debugging. /// A break-point for debugging.
@ -158,7 +213,6 @@ impl BreakPoint {
} }
/// A function call. /// A function call.
#[cfg(not(feature = "no_function"))]
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct CallStackFrame { pub struct CallStackFrame {
/// Function name. /// Function name.
@ -171,7 +225,6 @@ pub struct CallStackFrame {
pub pos: Position, pub pos: Position,
} }
#[cfg(not(feature = "no_function"))]
impl fmt::Display for CallStackFrame { impl fmt::Display for CallStackFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut fp = f.debug_tuple(&self.fn_name); let mut fp = f.debug_tuple(&self.fn_name);
@ -198,13 +251,12 @@ impl fmt::Display for CallStackFrame {
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct Debugger { pub struct Debugger {
/// The current status command. /// The current status command.
status: DebuggerCommand, pub(crate) status: DebuggerStatus,
/// The current state. /// The current state.
state: Dynamic, state: Dynamic,
/// The current set of break-points. /// The current set of break-points.
break_points: Vec<BreakPoint>, break_points: Vec<BreakPoint>,
/// The current function call stack. /// The current function call stack.
#[cfg(not(feature = "no_function"))]
call_stack: Vec<CallStackFrame>, call_stack: Vec<CallStackFrame>,
} }
@ -215,9 +267,9 @@ impl Debugger {
pub fn new(engine: &Engine) -> Self { pub fn new(engine: &Engine) -> Self {
Self { Self {
status: if engine.debugger.is_some() { status: if engine.debugger.is_some() {
DebuggerCommand::StepInto DebuggerStatus::STEP
} else { } else {
DebuggerCommand::Continue DebuggerStatus::CONTINUE
}, },
state: if let Some((ref init, _)) = engine.debugger { state: if let Some((ref init, _)) = engine.debugger {
init() init()
@ -225,7 +277,6 @@ impl Debugger {
Dynamic::UNIT Dynamic::UNIT
}, },
break_points: Vec::new(), break_points: Vec::new(),
#[cfg(not(feature = "no_function"))]
call_stack: Vec::new(), call_stack: Vec::new(),
} }
} }
@ -242,26 +293,17 @@ impl Debugger {
&mut self.state &mut self.state
} }
/// Get the current call stack. /// Get the current call stack.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn call_stack(&self) -> &[CallStackFrame] { pub fn call_stack(&self) -> &[CallStackFrame] {
&self.call_stack &self.call_stack
} }
/// Rewind the function call stack to a particular depth. /// Rewind the function call stack to a particular depth.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub(crate) fn rewind_call_stack(&mut self, len: usize) { pub(crate) fn rewind_call_stack(&mut self, len: usize) {
self.call_stack.truncate(len); self.call_stack.truncate(len);
} }
/// Add a new frame to the function call stack. /// Add a new frame to the function call stack.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub(crate) fn push_call_stack_frame( pub(crate) fn push_call_stack_frame(
&mut self, &mut self,
@ -277,34 +319,37 @@ impl Debugger {
pos, pos,
}); });
} }
/// Get the current status of this [`Debugger`]. /// Change the current status to [`CONTINUE`][DebuggerStatus::CONTINUE] and return the previous status.
#[inline(always)] pub(crate) fn clear_status_if(
#[must_use] &mut self,
pub fn status(&self) -> DebuggerCommand { filter: impl Fn(&DebuggerStatus) -> bool,
self.status ) -> Option<DebuggerStatus> {
} if filter(&self.status) {
/// Get a mutable reference to the current status of this [`Debugger`]. Some(mem::replace(&mut self.status, DebuggerStatus::CONTINUE))
#[inline(always)] } else {
#[must_use] None
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? /// Override the status of this [`Debugger`] if it is [`Some`] the current status is
/// [`CONTINUE`][DebuggerStatus::CONTINUE].
#[inline(always)]
pub(crate) fn reset_status(&mut self, status: Option<DebuggerStatus>) {
if self.status == DebuggerStatus::CONTINUE {
if let Some(cmd) = status {
self.status = cmd;
}
}
}
/// Returns the first break-point triggered by a particular [`AST` Node][ASTNode].
#[must_use] #[must_use]
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool { pub fn is_break_point(&self, src: &str, node: ASTNode) -> Option<usize> {
let _src = src; let _src = src;
self.break_points() self.break_points()
.iter() .iter()
.filter(|&bp| bp.is_enabled()) .enumerate()
.any(|bp| match bp { .filter(|&(_, bp)| bp.is_enabled())
.find(|&(_, bp)| match bp {
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false, BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
@ -316,13 +361,15 @@ impl Debugger {
node.position() == *pos && _src == source node.position() == *pos && _src == source
} }
BreakPoint::AtFunctionName { name, .. } => match node { BreakPoint::AtFunctionName { name, .. } => match node {
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => { ASTNode::Expr(Expr::FnCall(x, _))
x.name == *name | ASTNode::Stmt(Stmt::FnCall(x, _))
} | ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, _))) => x.name == *name,
_ => false, _ => false,
}, },
BreakPoint::AtFunctionCall { name, args, .. } => match node { BreakPoint::AtFunctionCall { name, args, .. } => match node {
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => { ASTNode::Expr(Expr::FnCall(x, _))
| ASTNode::Stmt(Stmt::FnCall(x, _))
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, _))) => {
x.args.len() == *args && x.name == *name x.args.len() == *args && x.name == *name
} }
_ => false, _ => false,
@ -333,6 +380,7 @@ impl Debugger {
_ => false, _ => false,
}, },
}) })
.map(|(i, _)| i)
} }
/// Get a slice of all [`BreakPoint`]'s. /// Get a slice of all [`BreakPoint`]'s.
#[inline(always)] #[inline(always)]
@ -349,7 +397,7 @@ impl Debugger {
} }
impl Engine { impl Engine {
/// Run the debugger callback. /// Run the debugger callback if there is a debugging interface registered.
#[inline(always)] #[inline(always)]
pub(crate) fn run_debugger<'a>( pub(crate) fn run_debugger<'a>(
&self, &self,
@ -361,26 +409,23 @@ impl Engine {
node: impl Into<ASTNode<'a>>, node: impl Into<ASTNode<'a>>,
level: usize, level: usize,
) -> RhaiResultOf<()> { ) -> RhaiResultOf<()> {
if let Some(cmd) = if self.debugger.is_some() {
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)? if let Some(cmd) =
{ self.run_debugger_with_reset_raw(scope, global, state, lib, this_ptr, node, level)?
global.debugger.status = cmd; {
global.debugger.status = cmd;
}
} }
Ok(()) Ok(())
} }
/// Run the debugger callback. /// Run the debugger callback if there is a debugging interface registered.
/// ///
/// Returns `true` if the debugger needs to be reactivated at the end of the block, statement or /// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
/// function call. /// 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. /// It is up to the [`Engine`] to reactivate the debugger.
#[inline] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn run_debugger_with_reset<'a>( pub(crate) fn run_debugger_with_reset<'a>(
&self, &self,
@ -391,61 +436,125 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>, node: impl Into<ASTNode<'a>>,
level: usize, level: usize,
) -> RhaiResultOf<Option<DebuggerCommand>> { ) -> RhaiResultOf<Option<DebuggerStatus>> {
if let Some((_, ref on_debugger)) = self.debugger { if self.debugger.is_some() {
let node = node.into(); self.run_debugger_with_reset_raw(scope, global, state, lib, this_ptr, node, level)
} else {
Ok(None)
}
}
/// Run the debugger callback.
///
/// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
/// function call.
///
/// It is up to the [`Engine`] to reactivate the debugger.
#[inline]
#[must_use]
pub(crate) fn run_debugger_with_reset_raw<'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<DebuggerStatus>> {
let node = node.into();
// Skip transitive nodes // Skip transitive nodes
match node { match node {
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None), ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
_ => (), _ => (),
} }
let stop = match global.debugger.status { let stop = match global.debugger.status {
DebuggerCommand::Continue => false, DebuggerStatus::Next(false, false) => false,
DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)), DebuggerStatus::Next(true, false) => matches!(node, ASTNode::Stmt(_)),
DebuggerCommand::StepInto | DebuggerCommand::StepOver => true, DebuggerStatus::Next(false, true) => matches!(node, ASTNode::Expr(_)),
}; DebuggerStatus::Next(true, true) => true,
DebuggerStatus::FunctionExit(_) => false,
};
if !stop && !global.debugger.is_break_point(&global.source, node) { let event = if stop {
DebuggerEvent::Step
} else {
if let Some(bp) = global.debugger.is_break_point(&global.source, node) {
DebuggerEvent::BreakPoint(bp)
} else {
return Ok(None); return Ok(None);
} }
};
let source = global.source.clone(); self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
let source = if source.is_empty() { }
None /// Run the debugger callback unconditionally.
} else { ///
Some(source.as_str()) /// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
}; /// function call.
///
/// It is up to the [`Engine`] to reactivate the debugger.
#[inline]
#[must_use]
pub(crate) fn run_debugger_raw<'a>(
&self,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
node: ASTNode<'a>,
event: DebuggerEvent,
level: usize,
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
let source = global.source.clone();
let source = if source.is_empty() {
None
} else {
Some(source.as_str())
};
let mut context = crate::EvalContext { let mut context = crate::EvalContext {
engine: self, engine: self,
scope, scope,
global, global,
state, state,
lib, lib,
this_ptr, this_ptr,
level, level,
}; };
let command = on_debugger(&mut context, node, source, node.position())?; if let Some((_, ref on_debugger)) = self.debugger {
let command = on_debugger(&mut context, event, node, source, node.position())?;
match command { match command {
DebuggerCommand::Continue => { DebuggerCommand::Continue => {
global.debugger.status = DebuggerCommand::Continue; global.debugger.status = DebuggerStatus::CONTINUE;
Ok(None) Ok(None)
} }
DebuggerCommand::Next => { DebuggerCommand::Next => {
global.debugger.status = DebuggerCommand::Continue; global.debugger.status = DebuggerStatus::CONTINUE;
Ok(Some(DebuggerCommand::Next)) Ok(Some(DebuggerStatus::NEXT))
}
DebuggerCommand::StepInto => {
global.debugger.status = DebuggerCommand::StepInto;
Ok(None)
} }
DebuggerCommand::StepOver => { DebuggerCommand::StepOver => {
global.debugger.status = DebuggerCommand::Continue; global.debugger.status = DebuggerStatus::CONTINUE;
Ok(Some(DebuggerCommand::StepOver)) Ok(Some(DebuggerStatus::STEP))
}
DebuggerCommand::StepInto => {
global.debugger.status = DebuggerStatus::STEP;
Ok(None)
}
DebuggerCommand::FunctionExit => {
// Bump a level if it is a function call
let level = match node {
ASTNode::Expr(Expr::FnCall(_, _))
| ASTNode::Stmt(Stmt::FnCall(_, _))
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(_, _))) => context.call_level() + 1,
_ => context.call_level(),
};
global.debugger.status = DebuggerStatus::FunctionExit(level);
Ok(None)
} }
} }
} else { } else {

View File

@ -50,17 +50,22 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
level: usize,
) -> RhaiResultOf<(Target<'s>, Position)> { ) -> RhaiResultOf<(Target<'s>, Position)> {
match expr { match expr {
Expr::Variable(Some(_), _, _) => { Expr::Variable(Some(_), _, _) => {
self.search_scope_only(scope, global, state, lib, this_ptr, expr) self.search_scope_only(scope, global, state, lib, this_ptr, expr, level)
} }
Expr::Variable(None, _var_pos, v) => match v.as_ref() { Expr::Variable(None, _var_pos, v) => match v.as_ref() {
// Normal variable access // Normal variable access
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
(_, None, _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr), (_, None, _) => {
self.search_scope_only(scope, global, state, lib, this_ptr, expr, level)
}
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
(_, (), _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr), (_, (), _) => {
self.search_scope_only(scope, global, state, lib, this_ptr, expr, level)
}
// Qualified variable access // Qualified variable access
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -136,6 +141,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
level: usize,
) -> RhaiResultOf<(Target<'s>, Position)> { ) -> RhaiResultOf<(Target<'s>, Position)> {
// Make sure that the pointer indirection is taken only when absolutely necessary. // Make sure that the pointer indirection is taken only when absolutely necessary.
@ -148,7 +154,7 @@ impl Engine {
Err(ERR::ErrorUnboundThis(*pos).into()) Err(ERR::ErrorUnboundThis(*pos).into())
} }
} }
_ if state.always_search_scope => (0, expr.position()), _ if state.always_search_scope => (0, expr.start_position()),
Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos), Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos),
Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos), Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos),
_ => unreachable!("Expr::Variable expected but gets {:?}", expr), _ => unreachable!("Expr::Variable expected but gets {:?}", expr),
@ -163,7 +169,7 @@ impl Engine {
state, state,
lib, lib,
this_ptr, this_ptr,
level: 0, level,
}; };
match resolve_var( match resolve_var(
expr.get_variable_name(true).expect("`Expr::Variable`"), expr.get_variable_name(true).expect("`Expr::Variable`"),
@ -236,8 +242,8 @@ impl Engine {
); );
self.make_function_call( self.make_function_call(
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes, pos, scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes,
*capture, level, *capture, pos, level,
) )
} }
@ -297,7 +303,7 @@ impl Engine {
.cloned() .cloned()
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into()) .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
} else { } else {
self.search_namespace(scope, global, state, lib, this_ptr, expr) self.search_namespace(scope, global, state, lib, this_ptr, expr, level)
.map(|(val, _)| val.take_or_clone()) .map(|(val, _)| val.take_or_clone())
}; };
} }
@ -345,12 +351,13 @@ impl Engine {
&mut (&mut concat).into(), &mut (&mut concat).into(),
("", Position::NONE), ("", Position::NONE),
item, item,
level,
) { ) {
result = Err(err.fill_position(expr.position())); result = Err(err.fill_position(expr.start_position()));
break; break;
} }
pos = expr.position(); pos = expr.start_position();
} }
result.map(|_| concat) result.map(|_| concat)
@ -503,7 +510,7 @@ impl Engine {
let result = (custom_def.func)(&mut context, &expressions); let result = (custom_def.func)(&mut context, &expressions);
self.check_return_value(result, expr.position()) self.check_return_value(result, expr.start_position())
} }
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),

View File

@ -5,6 +5,12 @@ use crate::{Engine, Identifier};
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{fmt, marker::PhantomData}; use std::{fmt, marker::PhantomData};
/// Collection of globally-defined constants.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub type GlobalConstants =
crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>;
/// _(internals)_ Global runtime states. /// _(internals)_ Global runtime states.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
// //
@ -44,9 +50,7 @@ pub struct GlobalRuntimeState<'a> {
/// Interior mutability is needed because it is shared in order to aid in cloning. /// Interior mutability is needed because it is shared in order to aid in cloning.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub(crate) constants: Option< pub(crate) constants: Option<GlobalConstants>,
crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>,
>,
/// Debugging interface. /// Debugging interface.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
pub debugger: super::Debugger, pub debugger: super::Debugger,

View File

@ -11,11 +11,14 @@ mod target;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
pub use chaining::{ChainArgument, ChainType}; pub use chaining::{ChainArgument, ChainType};
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))] pub use debugger::{
pub use debugger::CallStackFrame; BreakPoint, CallStackFrame, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus,
#[cfg(feature = "debugging")] OnDebuggerCallback, OnDebuggingInit,
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback, OnDebuggingInit}; };
pub use eval_context::EvalContext; pub use eval_context::EvalContext;
pub use eval_state::EvalState; pub use eval_state::EvalState;
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub use global_state::GlobalConstants;
pub use global_state::GlobalRuntimeState; pub use global_state::GlobalRuntimeState;
pub use target::{calc_index, calc_offset_len, Target}; pub use target::{calc_index, calc_offset_len, Target};

View File

@ -1,6 +1,6 @@
//! Module defining functions for evaluating a statement. //! Module defining functions for evaluating a statement.
use super::{EvalState, GlobalRuntimeState, Target}; use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
use crate::ast::{ use crate::ast::{
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
}; };
@ -120,6 +120,7 @@ impl Engine {
target: &mut Target, target: &mut Target,
root: (&str, Position), root: (&str, Position),
new_val: Dynamic, new_val: Dynamic,
level: usize,
) -> RhaiResultOf<()> { ) -> RhaiResultOf<()> {
if target.is_read_only() { if target.is_read_only() {
// Assignment to constant variable // Assignment to constant variable
@ -152,9 +153,10 @@ impl Engine {
let hash = hash_op_assign; let hash = hash_op_assign;
let args = &mut [lhs_ptr_inner, &mut new_val]; let args = &mut [lhs_ptr_inner, &mut new_val];
let level = level + 1;
match self.call_native_fn( match self.call_native_fn(
global, state, lib, op_assign, hash, args, true, true, op_pos, global, state, lib, op_assign, hash, args, true, true, op_pos, level,
) { ) {
Ok(_) => { Ok(_) => {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -164,7 +166,7 @@ impl Engine {
{ {
// Expand to `var = var op rhs` // Expand to `var = var op rhs`
let (value, _) = self.call_native_fn( let (value, _) = self.call_native_fn(
global, state, lib, op, hash_op, args, true, false, op_pos, global, state, lib, op, hash_op, args, true, false, op_pos, level,
)?; )?;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -238,7 +240,7 @@ impl Engine {
if let Ok(rhs_val) = rhs_result { if let Ok(rhs_val) = rhs_result {
let search_result = let search_result =
self.search_namespace(scope, global, state, lib, this_ptr, lhs); self.search_namespace(scope, global, state, lib, this_ptr, lhs, level);
if let Ok(search_val) = search_result { if let Ok(search_val) = search_result {
let (mut lhs_ptr, pos) = search_val; let (mut lhs_ptr, pos) = search_val;
@ -263,8 +265,9 @@ impl Engine {
&mut lhs_ptr, &mut lhs_ptr,
(var_name, pos), (var_name, pos),
rhs_val, rhs_val,
level,
) )
.map_err(|err| err.fill_position(rhs.position())) .map_err(|err| err.fill_position(rhs.start_position()))
.map(|_| Dynamic::UNIT) .map(|_| Dynamic::UNIT)
} else { } else {
search_result.map(|_| Dynamic::UNIT) search_result.map(|_| Dynamic::UNIT)
@ -280,7 +283,7 @@ impl Engine {
.map(Dynamic::flatten); .map(Dynamic::flatten);
if let Ok(rhs_val) = rhs_result { if let Ok(rhs_val) = rhs_result {
let _new_val = Some(((rhs_val, rhs.position()), (*op_info, *op_pos))); let _new_val = Some(((rhs_val, rhs.start_position()), (*op_info, *op_pos)));
// Must be either `var[index] op= val` or `var.prop op= val` // Must be either `var[index] op= val` or `var.prop op= val`
match lhs { match lhs {
@ -683,7 +686,7 @@ impl Engine {
loop_result loop_result
} else { } else {
Err(ERR::ErrorFor(expr.position()).into()) Err(ERR::ErrorFor(expr.start_position()).into())
} }
} else { } else {
iter_result iter_result
@ -799,9 +802,14 @@ impl Engine {
// Empty return // Empty return
Stmt::Return(_, None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()), Stmt::Return(_, None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
// Let/const statement - shadowing disallowed
Stmt::Var(_, x, _, pos) if !self.allow_shadowing() && scope.contains(&x.name) => {
Err(ERR::ErrorVariableExists(x.name.to_string(), *pos).into())
}
// Let/const statement // Let/const statement
Stmt::Var(expr, x, options, _) => { Stmt::Var(expr, x, options, pos) => {
let var_name = &x.name; let var_name = &x.name;
let entry_type = if options.contains(AST_OPTION_CONSTANT) { let entry_type = if options.contains(AST_OPTION_CONSTANT) {
AccessMode::ReadOnly AccessMode::ReadOnly
} else { } else {
@ -809,48 +817,79 @@ impl Engine {
}; };
let export = options.contains(AST_OPTION_EXPORTED); let export = options.contains(AST_OPTION_EXPORTED);
let value_result = self let result = if let Some(ref filter) = self.def_var_filter {
.eval_expr(scope, global, state, lib, this_ptr, expr, level) let shadowing = scope.contains(var_name);
.map(Dynamic::flatten); let scope_level = state.scope_level;
let is_const = entry_type == AccessMode::ReadOnly;
if let Ok(value) = value_result { let context = EvalContext {
let _alias = if !rewind_scope { engine: self,
#[cfg(not(feature = "no_function"))] scope,
#[cfg(not(feature = "no_module"))] global,
if state.scope_level == 0 state,
&& entry_type == AccessMode::ReadOnly lib,
&& lib.iter().any(|&m| !m.is_empty()) this_ptr,
{ level: level,
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 {
Some(var_name)
} else {
None
}
} else if export {
unreachable!("exported variable not on global level");
} else {
None
}; };
scope.push_dynamic_value(var_name.clone(), entry_type, value); match filter(var_name, is_const, scope_level, shadowing, &context) {
Ok(true) => None,
#[cfg(not(feature = "no_module"))] Ok(false) => Some(Err(ERR::ErrorRuntime(
if let Some(alias) = _alias { format!("Variable cannot be defined: {}", var_name).into(),
scope.add_entry_alias(scope.len() - 1, alias.clone()); *pos,
)
.into())),
err @ Err(_) => Some(err),
} }
Ok(Dynamic::UNIT)
} else { } else {
value_result None
};
if let Some(result) = result {
result.map(|_| Dynamic::UNIT)
} else {
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"))]
if state.scope_level == 0
&& entry_type == AccessMode::ReadOnly
&& lib.iter().any(|&m| !m.is_empty())
{
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 {
Some(var_name)
} else {
None
}
} else if export {
unreachable!("exported variable not on global level");
} else {
None
};
scope.push_dynamic_value(var_name.clone(), entry_type, value);
#[cfg(not(feature = "no_module"))]
if let Some(alias) = _alias {
scope.add_entry_alias(scope.len() - 1, alias.clone());
}
Ok(Dynamic::UNIT)
} else {
value_result
}
} }
} }
@ -866,9 +905,10 @@ impl Engine {
let path_result = self let path_result = self
.eval_expr(scope, global, state, lib, this_ptr, &expr, level) .eval_expr(scope, global, state, lib, this_ptr, &expr, level)
.and_then(|v| { .and_then(|v| {
let typ = v.type_name();
v.try_cast::<crate::ImmutableString>().ok_or_else(|| { v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<crate::ImmutableString>( self.make_type_mismatch_err::<crate::ImmutableString>(
"", typ,
expr.position(), expr.position(),
) )
}) })
@ -877,7 +917,7 @@ impl Engine {
if let Ok(path) = path_result { if let Ok(path) = path_result {
use crate::ModuleResolver; use crate::ModuleResolver;
let path_pos = expr.position(); let path_pos = expr.start_position();
let resolver = global.embedded_module_resolver.clone(); let resolver = global.embedded_module_resolver.clone();

View File

@ -328,6 +328,8 @@ impl Engine {
result.as_ref().map(Box::as_ref) result.as_ref().map(Box::as_ref)
} }
/// # Main Entry-Point
///
/// Call a native Rust function registered with the [`Engine`]. /// Call a native Rust function registered with the [`Engine`].
/// ///
/// # WARNING /// # WARNING
@ -347,6 +349,7 @@ impl Engine {
is_ref_mut: bool, is_ref_mut: bool,
is_op_assign: bool, is_op_assign: bool,
pos: Position, pos: Position,
level: usize,
) -> RhaiResultOf<(Dynamic, bool)> { ) -> RhaiResultOf<(Dynamic, bool)> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, pos)?; self.inc_operations(&mut global.num_operations, pos)?;
@ -365,43 +368,92 @@ impl Engine {
is_op_assign, is_op_assign,
); );
if let Some(FnResolutionCacheEntry { func, source }) = func { if func.is_some() {
assert!(func.is_native()); let is_method = func.map(|f| f.func.is_method()).unwrap_or(false);
// Calling pure function but the first argument is a reference? // Push a new call stack frame
let mut backup: Option<ArgBackup> = None; #[cfg(feature = "debugging")]
if is_ref_mut && func.is_pure() && !args.is_empty() { let orig_call_stack_len = global.debugger.call_stack().len();
// Clone the first argument
backup = Some(ArgBackup::new());
backup
.as_mut()
.expect("`Some`")
.change_first_arg_to_copy(args);
}
// Run external function let mut _result = if let Some(FnResolutionCacheEntry { func, source }) = func {
let source = match (source.as_str(), parent_source.as_str()) { assert!(func.is_native());
("", "") => None,
("", s) | (s, _) => Some(s),
};
let context = (self, name, source, &*global, lib, pos).into(); // Calling pure function but the first argument is a reference?
let mut backup: Option<ArgBackup> = None;
if is_ref_mut && func.is_pure() && !args.is_empty() {
// Clone the first argument
backup = Some(ArgBackup::new());
backup
.as_mut()
.expect("`Some`")
.change_first_arg_to_copy(args);
}
let result = if func.is_plugin_fn() { let source = match (source.as_str(), parent_source.as_str()) {
func.get_plugin_fn() ("", "") => None,
.expect("plugin function") ("", s) | (s, _) => Some(s),
.call(context, args) };
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
global.debugger.push_call_stack_frame(
name,
args.iter().map(|v| (*v).clone()).collect(),
source.unwrap_or(""),
pos,
);
}
// Run external function
let context = (self, name, source, &*global, lib, pos, level).into();
let result = if func.is_plugin_fn() {
func.get_plugin_fn()
.expect("plugin function")
.call(context, args)
} else {
func.get_native_fn().expect("native function")(context, args)
};
// Restore the original reference
if let Some(bk) = backup {
bk.restore_first_arg(args)
}
result
} else { } else {
func.get_native_fn().expect("native function")(context, args) unreachable!("`Some`");
}; };
// Restore the original reference #[cfg(feature = "debugging")]
if let Some(bk) = backup { {
bk.restore_first_arg(args) let trigger = match global.debugger.status {
crate::eval::DebuggerStatus::FunctionExit(n) => n >= level,
crate::eval::DebuggerStatus::Next(_, true) => true,
_ => false,
};
if trigger {
let scope = &mut &mut Scope::new();
let node = crate::ast::Stmt::Noop(pos);
let node = (&node).into();
let event = match _result {
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
};
match self
.run_debugger_raw(scope, global, state, lib, &mut None, node, event, level)
{
Ok(_) => (),
Err(err) => _result = Err(err),
}
}
// Pop the call stack
global.debugger.rewind_call_stack(orig_call_stack_len);
} }
// Check the return value (including data sizes) // Check the return value (including data sizes)
let result = self.check_return_value(result, pos)?; let result = self.check_return_value(_result, pos)?;
// Check the data size of any `&mut` object, which may be changed. // Check the data size of any `&mut` object, which may be changed.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -443,7 +495,7 @@ impl Engine {
(Dynamic::UNIT, false) (Dynamic::UNIT, false)
} }
} }
_ => (result, func.is_method()), _ => (result, is_method),
}); });
} }
@ -530,6 +582,8 @@ impl Engine {
} }
} }
/// # Main Entry-Point
///
/// Perform an actual function call, native Rust or scripted, taking care of special functions. /// Perform an actual function call, native Rust or scripted, taking care of special functions.
/// ///
/// # WARNING /// # WARNING
@ -540,6 +594,7 @@ impl Engine {
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
pub(crate) fn exec_fn_call( pub(crate) fn exec_fn_call(
&self, &self,
scope: Option<&mut Scope>,
global: &mut GlobalRuntimeState, global: &mut GlobalRuntimeState,
state: &mut EvalState, state: &mut EvalState,
lib: &[&Module], lib: &[&Module],
@ -549,7 +604,6 @@ impl Engine {
is_ref_mut: bool, is_ref_mut: bool,
is_method_call: bool, is_method_call: bool,
pos: Position, pos: Position,
scope: Option<&mut Scope>,
level: usize, level: usize,
) -> RhaiResultOf<(Dynamic, bool)> { ) -> RhaiResultOf<(Dynamic, bool)> {
fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> { fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> {
@ -562,7 +616,6 @@ impl Engine {
ensure_no_data_race(fn_name, args, is_ref_mut)?; ensure_no_data_race(fn_name, args, is_ref_mut)?;
let _scope = scope; let _scope = scope;
let _level = level;
let _is_method_call = is_method_call; let _is_method_call = is_method_call;
// These may be redirected from method style calls. // These may be redirected from method style calls.
@ -612,6 +665,8 @@ impl Engine {
_ => (), _ => (),
} }
let level = level + 1;
// Script-defined function call? // Script-defined function call?
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some(FnResolutionCacheEntry { func, mut source }) = self if let Some(FnResolutionCacheEntry { func, mut source }) = self
@ -651,8 +706,6 @@ impl Engine {
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
let (first_arg, rest_args) = args.split_first_mut().unwrap(); let (first_arg, rest_args) = args.split_first_mut().unwrap();
let level = _level + 1;
let result = self.call_script_fn( let result = self.call_script_fn(
scope, scope,
global, global,
@ -661,8 +714,8 @@ impl Engine {
&mut Some(*first_arg), &mut Some(*first_arg),
func, func,
rest_args, rest_args,
pos,
true, true,
pos,
level, level,
); );
@ -679,10 +732,8 @@ impl Engine {
.change_first_arg_to_copy(args); .change_first_arg_to_copy(args);
} }
let level = _level + 1;
let result = self.call_script_fn( let result = self.call_script_fn(
scope, global, state, lib, &mut None, func, args, pos, true, level, scope, global, state, lib, &mut None, func, args, true, pos, level,
); );
// Restore the original reference // Restore the original reference
@ -702,7 +753,7 @@ impl Engine {
// Native function call // Native function call
let hash = hashes.native; let hash = hashes.native;
self.call_native_fn( self.call_native_fn(
global, state, lib, fn_name, hash, args, is_ref_mut, false, pos, global, state, lib, fn_name, hash, args, is_ref_mut, false, pos, level,
) )
} }
@ -764,7 +815,7 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
global, state, lib, fn_name, new_hash, &mut args, false, false, pos, None, None, global, state, lib, fn_name, new_hash, &mut args, false, false, pos,
level, level,
) )
} }
@ -804,7 +855,7 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
global, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos, None, None, global, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos,
level, level,
) )
} }
@ -876,7 +927,7 @@ impl Engine {
args.extend(call_args.iter_mut()); args.extend(call_args.iter_mut());
self.exec_fn_call( self.exec_fn_call(
global, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos, None, None, global, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos,
level, level,
) )
} }
@ -901,9 +952,9 @@ impl Engine {
state: &mut EvalState, state: &mut EvalState,
lib: &[&Module], lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
level: usize,
arg_expr: &Expr, arg_expr: &Expr,
constants: &[Dynamic], constants: &[Dynamic],
level: usize,
) -> RhaiResultOf<(Dynamic, Position)> { ) -> RhaiResultOf<(Dynamic, Position)> {
Ok(( Ok((
if let Expr::Stack(slot, _) = arg_expr { if let Expr::Stack(slot, _) = arg_expr {
@ -915,9 +966,21 @@ impl Engine {
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?; self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
value value
} else { } else {
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)? // Do not match function exit for arguments
#[cfg(feature = "debugging")]
let reset_debugger = global.debugger.clear_status_if(|status| {
matches!(status, crate::eval::DebuggerStatus::FunctionExit(_))
});
let result = self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level);
// Restore function exit status
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
result?
}, },
arg_expr.position(), arg_expr.start_position(),
)) ))
} }
@ -934,8 +997,8 @@ impl Engine {
args_expr: &[Expr], args_expr: &[Expr],
constants: &[Dynamic], constants: &[Dynamic],
hashes: FnCallHashes, hashes: FnCallHashes,
pos: Position,
capture_scope: bool, capture_scope: bool,
pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
let mut first_arg = first_arg; let mut first_arg = first_arg;
@ -951,7 +1014,7 @@ impl Engine {
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
let (arg_value, arg_pos) = let (arg_value, arg_pos) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?; self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
if !arg_value.is::<FnPtr>() { if !arg_value.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>( return Err(self.make_type_mismatch_err::<FnPtr>(
@ -986,7 +1049,7 @@ impl Engine {
KEYWORD_FN_PTR if total_args == 1 => { KEYWORD_FN_PTR if total_args == 1 => {
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
let (arg_value, arg_pos) = let (arg_value, arg_pos) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?; self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
// Fn - only in function call style // Fn - only in function call style
return arg_value return arg_value
@ -1001,7 +1064,7 @@ impl Engine {
KEYWORD_FN_PTR_CURRY if total_args > 1 => { KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let first = first_arg.unwrap(); let first = first_arg.unwrap();
let (arg_value, arg_pos) = self let (arg_value, arg_pos) = self
.get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?; .get_arg_value(scope, global, state, lib, this_ptr, first, constants, level)?;
if !arg_value.is::<FnPtr>() { if !arg_value.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>( return Err(self.make_type_mismatch_err::<FnPtr>(
@ -1018,7 +1081,7 @@ impl Engine {
.iter() .iter()
.try_fold(fn_curry, |mut curried, expr| -> RhaiResultOf<_> { .try_fold(fn_curry, |mut curried, expr| -> RhaiResultOf<_> {
let (value, _) = self.get_arg_value( let (value, _) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants, scope, global, state, lib, this_ptr, expr, constants, level,
)?; )?;
curried.push(value); curried.push(value);
Ok(curried) Ok(curried)
@ -1032,7 +1095,7 @@ impl Engine {
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
let (arg_value, _) = let (arg_value, _) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?; self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
return Ok(arg_value.is_shared().into()); return Ok(arg_value.is_shared().into());
} }
@ -1041,14 +1104,14 @@ impl Engine {
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
let first = first_arg.unwrap(); let first = first_arg.unwrap();
let (arg_value, arg_pos) = self let (arg_value, arg_pos) = self
.get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?; .get_arg_value(scope, global, state, lib, this_ptr, first, constants, level)?;
let fn_name = arg_value let fn_name = arg_value
.into_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
let (arg_value, arg_pos) = self.get_arg_value( let (arg_value, arg_pos) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, &a_expr[0], constants, scope, global, state, lib, this_ptr, &a_expr[0], constants, level,
)?; )?;
let num_params = arg_value let num_params = arg_value
@ -1068,7 +1131,7 @@ impl Engine {
KEYWORD_IS_DEF_VAR if total_args == 1 => { KEYWORD_IS_DEF_VAR if total_args == 1 => {
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
let (arg_value, arg_pos) = let (arg_value, arg_pos) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?; self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
let var_name = arg_value let var_name = arg_value
.into_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
@ -1081,7 +1144,7 @@ impl Engine {
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
let (arg_value, pos) = let (arg_value, pos) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?; self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
let script = &arg_value let script = &arg_value
.into_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
@ -1130,7 +1193,7 @@ impl Engine {
.map(|&v| v) .map(|&v| v)
.chain(a_expr.iter()) .chain(a_expr.iter())
.try_for_each(|expr| { .try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
.map(|(value, _)| arg_values.push(value.flatten())) .map(|(value, _)| arg_values.push(value.flatten()))
})?; })?;
args.extend(curry.iter_mut()); args.extend(curry.iter_mut());
@ -1141,7 +1204,7 @@ impl Engine {
return self return self
.exec_fn_call( .exec_fn_call(
global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, scope, scope, global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos,
level, level,
) )
.map(|(v, _)| v); .map(|(v, _)| v);
@ -1162,12 +1225,12 @@ impl Engine {
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
a_expr.iter().try_for_each(|expr| { a_expr.iter().try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
.map(|(value, _)| arg_values.push(value.flatten())) .map(|(value, _)| arg_values.push(value.flatten()))
})?; })?;
let (mut target, _pos) = let (mut target, _pos) =
self.search_namespace(scope, global, state, lib, this_ptr, first_expr)?; self.search_namespace(scope, global, state, lib, this_ptr, first_expr, level)?;
if target.as_ref().is_read_only() { if target.as_ref().is_read_only() {
target = target.into_owned(); target = target.into_owned();
@ -1198,7 +1261,7 @@ impl Engine {
.chain(a_expr.iter()) .chain(a_expr.iter())
.try_for_each(|expr| { .try_for_each(|expr| {
self.get_arg_value( self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants, scope, global, state, lib, this_ptr, expr, constants, level,
) )
.map(|(value, _)| arg_values.push(value.flatten())) .map(|(value, _)| arg_values.push(value.flatten()))
})?; })?;
@ -1208,7 +1271,7 @@ impl Engine {
} }
self.exec_fn_call( self.exec_fn_call(
global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, None, level, None, global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
} }
@ -1248,13 +1311,14 @@ impl Engine {
arg_values.push(Dynamic::UNIT); arg_values.push(Dynamic::UNIT);
args_expr.iter().skip(1).try_for_each(|expr| { args_expr.iter().skip(1).try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
.map(|(value, _)| arg_values.push(value.flatten())) .map(|(value, _)| arg_values.push(value.flatten()))
})?; })?;
// Get target reference to first argument // Get target reference to first argument
let first_arg = &args_expr[0];
let (target, _pos) = let (target, _pos) =
self.search_scope_only(scope, global, state, lib, this_ptr, &args_expr[0])?; self.search_scope_only(scope, global, state, lib, this_ptr, first_arg, level)?;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, _pos)?; self.inc_operations(&mut global.num_operations, _pos)?;
@ -1278,7 +1342,7 @@ impl Engine {
} else { } else {
// func(..., ...) or func(mod::x, ...) // func(..., ...) or func(mod::x, ...)
args_expr.iter().try_for_each(|expr| { args_expr.iter().try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
.map(|(value, _)| arg_values.push(value.flatten())) .map(|(value, _)| arg_values.push(value.flatten()))
})?; })?;
args.extend(arg_values.iter_mut()); args.extend(arg_values.iter_mut());
@ -1312,34 +1376,27 @@ impl Engine {
} }
} }
let level = level + 1;
match func { match func {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Some(f) if f.is_script() => { Some(f) if f.is_script() => {
let fn_def = f.get_script_fn_def().expect("script-defined function"); let fn_def = f.get_script_fn_def().expect("script-defined function");
let new_scope = &mut Scope::new();
let mut source = module.id_raw().clone();
mem::swap(&mut global.source, &mut source);
if fn_def.body.is_empty() { let result = self.call_script_fn(
Ok(Dynamic::UNIT) new_scope, global, state, lib, &mut None, fn_def, &mut args, true, pos, level,
} else { );
let new_scope = &mut Scope::new();
let mut source = module.id_raw().clone(); global.source = source;
mem::swap(&mut global.source, &mut source);
let level = level + 1; result
let result = self.call_script_fn(
new_scope, global, state, lib, &mut None, fn_def, &mut args, pos, true,
level,
);
global.source = source;
result
}
} }
Some(f) if f.is_plugin_fn() => { Some(f) if f.is_plugin_fn() => {
let context = (self, fn_name, module.id(), &*global, lib, pos).into(); let context = (self, fn_name, module.id(), &*global, lib, pos, level).into();
let result = f let result = f
.get_plugin_fn() .get_plugin_fn()
.expect("plugin function") .expect("plugin function")
@ -1350,7 +1407,7 @@ impl Engine {
Some(f) if f.is_native() => { Some(f) if f.is_native() => {
let func = f.get_native_fn().expect("native function"); let func = f.get_native_fn().expect("native function");
let context = (self, fn_name, module.id(), &*global, lib, pos).into(); let context = (self, fn_name, module.id(), &*global, lib, pos, level).into();
let result = func(context, &mut args); let result = func(context, &mut args);
self.check_return_value(result, pos) self.check_return_value(result, pos)
} }

View File

@ -70,6 +70,8 @@ pub struct NativeCallContext<'a> {
lib: &'a [&'a Module], lib: &'a [&'a Module],
/// [Position] of the function call. /// [Position] of the function call.
pos: Position, pos: Position,
/// The current nesting level of function calls.
level: usize,
} }
impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized> impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
@ -80,6 +82,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
&'a GlobalRuntimeState<'a>, &'a GlobalRuntimeState<'a>,
&'a M, &'a M,
Position, Position,
usize,
)> for NativeCallContext<'a> )> for NativeCallContext<'a>
{ {
#[inline(always)] #[inline(always)]
@ -91,6 +94,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
&'a GlobalRuntimeState, &'a GlobalRuntimeState,
&'a M, &'a M,
Position, Position,
usize,
), ),
) -> Self { ) -> Self {
Self { Self {
@ -100,6 +104,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
global: Some(value.3), global: Some(value.3),
lib: value.4.as_ref(), lib: value.4.as_ref(),
pos: value.5, pos: value.5,
level: value.6,
} }
} }
} }
@ -116,6 +121,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
global: None, global: None,
lib: value.2.as_ref(), lib: value.2.as_ref(),
pos: Position::NONE, pos: Position::NONE,
level: 0,
} }
} }
} }
@ -141,6 +147,7 @@ impl<'a> NativeCallContext<'a> {
global: None, global: None,
lib, lib,
pos: Position::NONE, pos: Position::NONE,
level: 0,
} }
} }
/// _(internals)_ Create a new [`NativeCallContext`]. /// _(internals)_ Create a new [`NativeCallContext`].
@ -158,6 +165,7 @@ impl<'a> NativeCallContext<'a> {
global: &'a GlobalRuntimeState, global: &'a GlobalRuntimeState,
lib: &'a [&Module], lib: &'a [&Module],
pos: Position, pos: Position,
level: usize,
) -> Self { ) -> Self {
Self { Self {
engine, engine,
@ -166,6 +174,7 @@ impl<'a> NativeCallContext<'a> {
global: Some(global), global: Some(global),
lib, lib,
pos, pos,
level,
} }
} }
/// The current [`Engine`]. /// The current [`Engine`].
@ -186,6 +195,12 @@ impl<'a> NativeCallContext<'a> {
pub const fn position(&self) -> Position { pub const fn position(&self) -> Position {
self.pos self.pos
} }
/// Current nesting level of function calls.
#[inline(always)]
#[must_use]
pub const fn call_level(&self) -> usize {
self.level
}
/// The current source. /// The current source.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
@ -306,6 +321,7 @@ impl<'a> NativeCallContext<'a> {
self.engine() self.engine()
.exec_fn_call( .exec_fn_call(
None,
&mut global, &mut global,
&mut state, &mut state,
self.lib, self.lib,
@ -315,8 +331,7 @@ impl<'a> NativeCallContext<'a> {
is_ref_mut, is_ref_mut,
is_method_call, is_method_call,
Position::NONE, Position::NONE,
None, self.level + 1,
0,
) )
.map(|(r, _)| r) .map(|(r, _)| r)
} }
@ -429,3 +444,11 @@ pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Optio
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnVarCallback = pub type OnVarCallback =
dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync; dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync;
/// Callback function for variable definition.
#[cfg(not(feature = "sync"))]
pub type OnDefVarCallback = dyn Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool>;
/// Callback function for variable definition.
#[cfg(feature = "sync")]
pub type OnDefVarCallback =
dyn Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool> + Send + Sync;

View File

@ -4,12 +4,14 @@
use super::call::FnCallArgs; use super::call::FnCallArgs;
use crate::ast::ScriptFnDef; use crate::ast::ScriptFnDef;
use crate::eval::{EvalState, GlobalRuntimeState}; use crate::eval::{EvalState, GlobalRuntimeState};
use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, StaticVec, ERR}; use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, ERR};
use std::mem; use std::mem;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
impl Engine { impl Engine {
/// # Main Entry-Point
///
/// Call a script-defined function. /// Call a script-defined function.
/// ///
/// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not. /// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not.
@ -29,8 +31,8 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
fn_def: &ScriptFnDef, fn_def: &ScriptFnDef,
args: &mut FnCallArgs, args: &mut FnCallArgs,
pos: Position,
rewind_scope: bool, rewind_scope: bool,
pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
#[inline(never)] #[inline(never)]
@ -41,13 +43,19 @@ impl Engine {
err: RhaiError, err: RhaiError,
pos: Position, pos: Position,
) -> RhaiResult { ) -> RhaiResult {
let _fn_def = fn_def;
#[cfg(not(feature = "no_module"))]
let source = _fn_def
.environ
.as_ref()
.and_then(|environ| environ.lib.id().map(str::to_string));
#[cfg(feature = "no_module")]
let source = None;
Err(ERR::ErrorInFunctionCall( Err(ERR::ErrorInFunctionCall(
name, name,
fn_def source.unwrap_or_else(|| global.source.to_string()),
.lib
.as_ref()
.and_then(|m| m.id().map(str::to_string))
.unwrap_or_else(|| global.source.to_string()),
err, err,
pos, pos,
) )
@ -59,22 +67,26 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, pos)?; self.inc_operations(&mut global.num_operations, pos)?;
if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
// Check for stack overflow // Check for stack overflow
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if level > self.max_call_levels() { if level > self.max_call_levels() {
return Err(ERR::ErrorStackOverflow(pos).into()); return Err(ERR::ErrorStackOverflow(pos).into());
} }
#[cfg(feature = "debugging")]
if self.debugger.is_none() && fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
#[cfg(not(feature = "debugging"))]
if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let orig_imports_len = global.num_imports(); let orig_imports_len = global.num_imports();
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))]
let orig_call_stack_len = global.debugger.call_stack().len(); let orig_call_stack_len = global.debugger.call_stack().len();
// Put arguments into scope as variables // Put arguments into scope as variables
@ -85,44 +97,58 @@ impl Engine {
// Push a new call stack frame // Push a new call stack frame
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))] if self.debugger.is_some() {
global.debugger.push_call_stack_frame( global.debugger.push_call_stack_frame(
fn_def.name.clone(), fn_def.name.clone(),
scope scope
.iter() .iter()
.skip(orig_scope_len) .skip(orig_scope_len)
.map(|(_, _, v)| v.clone()) .map(|(_, _, v)| v.clone())
.collect(), .collect(),
global.source.clone(), global.source.clone(),
pos, pos,
); );
}
// Merge in encapsulated environment, if any // 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(); let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
let lib = if let Some(ref fn_lib) = fn_def.lib { #[cfg(not(feature = "no_module"))]
if fn_lib.is_empty() { let mut lib_merged = crate::StaticVec::with_capacity(lib.len() + 1);
lib
} else {
state.push_fn_resolution_cache();
lib_merged.push(fn_lib.as_ref());
lib_merged.extend(lib.iter().cloned());
&lib_merged
}
} else {
lib
};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if let Some(ref modules) = fn_def.global { let (lib, constants) = if let Some(crate::ast::EncapsulatedEnviron {
for (n, m) in modules.iter().cloned() { lib: ref fn_lib,
ref imports,
ref constants,
}) = fn_def.environ
{
for (n, m) in imports.iter().cloned() {
global.push_import(n, m) global.push_import(n, m)
} }
(
if fn_lib.is_empty() {
lib
} else {
state.push_fn_resolution_cache();
lib_merged.push(fn_lib.as_ref());
lib_merged.extend(lib.iter().cloned());
&lib_merged
},
Some(mem::replace(&mut global.constants, constants.clone())),
)
} else {
(lib, None)
};
#[cfg(feature = "debugging")]
{
let node = crate::ast::Stmt::Noop(fn_def.body.position());
self.run_debugger(scope, global, state, lib, this_ptr, &node, level)?;
} }
// Evaluate the function // Evaluate the function
let result = self let mut _result = self
.eval_stmt_block( .eval_stmt_block(
scope, scope,
global, global,
@ -155,6 +181,31 @@ impl Engine {
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos), _ => make_error(fn_def.name.to_string(), fn_def, global, err, pos),
}); });
#[cfg(feature = "debugging")]
{
let trigger = match global.debugger.status {
crate::eval::DebuggerStatus::FunctionExit(n) => n >= level,
crate::eval::DebuggerStatus::Next(_, true) => true,
_ => false,
};
if trigger {
let node = crate::ast::Stmt::Noop(fn_def.body.end_position().or_else(pos));
let node = (&node).into();
let event = match _result {
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
};
match self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
{
Ok(_) => (),
Err(err) => _result = Err(err),
}
}
// Pop the call stack
global.debugger.rewind_call_stack(orig_call_stack_len);
}
// Remove all local variables and imported modules // Remove all local variables and imported modules
if rewind_scope { if rewind_scope {
scope.rewind(orig_scope_len); scope.rewind(orig_scope_len);
@ -165,15 +216,16 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
global.truncate_imports(orig_imports_len); global.truncate_imports(orig_imports_len);
// Restore constants
#[cfg(not(feature = "no_module"))]
if let Some(constants) = constants {
global.constants = constants;
}
// Restore state // Restore state
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
// Pop the call stack _result
#[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))]
global.debugger.rewind_call_stack(orig_call_stack_len);
result
} }
// Does a script-defined function exist? // Does a script-defined function exist?

View File

@ -154,6 +154,8 @@ pub use eval::EvalContext;
pub use func::{NativeCallContext, RegisterNativeFunction}; pub use func::{NativeCallContext, RegisterNativeFunction};
pub use module::{FnNamespace, Module}; pub use module::{FnNamespace, Module};
pub use tokenizer::Position; pub use tokenizer::Position;
#[cfg(not(feature = "no_std"))]
pub use types::Instant;
pub use types::{ pub use types::{
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope, Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
}; };
@ -164,7 +166,7 @@ pub use types::{
pub mod debugger { pub mod debugger {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use super::eval::CallStackFrame; pub use super::eval::CallStackFrame;
pub use super::eval::{BreakPoint, Debugger, DebuggerCommand}; pub use super::eval::{BreakPoint, Debugger, DebuggerCommand, DebuggerEvent};
} }
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
@ -256,9 +258,14 @@ pub use parser::ParseState;
pub use ast::{ pub use ast::{
ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock, OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
AST_OPTION_FLAGS::*, AST_OPTION_FLAGS,
}; };
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub use ast::EncapsulatedEnviron;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub use ast::FloatWrapper; pub use ast::FloatWrapper;

View File

@ -1696,33 +1696,21 @@ impl Module {
let mut module = Module::new(); let mut module = Module::new();
// Extra modules left become sub-modules // Extra modules left become sub-modules
#[cfg(not(feature = "no_function"))] let mut imports = StaticVec::new_const();
let mut func_global = None;
if result.is_ok() { if result.is_ok() {
global global
.scan_imports_raw() .scan_imports_raw()
.skip(orig_imports_len) .skip(orig_imports_len)
.for_each(|(k, m)| { .for_each(|(k, m)| {
#[cfg(not(feature = "no_function"))] imports.push((k.clone(), m.clone()));
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()); module.set_sub_module(k.clone(), m.clone());
}); });
} }
// Restore global state // Restore global state
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
{ let constants = std::mem::replace(&mut global.constants, orig_constants);
global.constants = orig_constants;
}
global.truncate_imports(orig_imports_len); global.truncate_imports(orig_imports_len);
global.source = orig_source; global.source = orig_source;
@ -1747,12 +1735,9 @@ impl Module {
} }
} }
#[cfg(not(feature = "no_function"))]
let func_global = func_global.map(|v| v.into_boxed_slice());
// Non-private functions defined become module functions // Non-private functions defined become module functions
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if ast.has_functions() { {
ast.shared_lib() ast.shared_lib()
.iter_fn() .iter_fn()
.filter(|&f| match f.metadata.access { .filter(|&f| match f.metadata.access {
@ -1761,15 +1746,20 @@ impl Module {
}) })
.filter(|&f| f.func.is_script()) .filter(|&f| f.func.is_script())
.for_each(|f| { .for_each(|f| {
// Encapsulate AST environment
let mut func = f let mut func = f
.func .func
.get_script_fn_def() .get_script_fn_def()
.expect("script-defined function") .expect("script-defined function")
.as_ref() .as_ref()
.clone(); .clone();
func.lib = Some(ast.shared_lib().clone());
func.global = func_global.clone(); // Encapsulate AST environment
func.environ = Some(crate::ast::EncapsulatedEnviron {
lib: ast.shared_lib().clone(),
imports: imports.clone().into_boxed_slice(),
constants: constants.clone(),
});
module.set_script_fn(func); module.set_script_fn(func);
}); });
} }

View File

@ -147,6 +147,7 @@ impl<'a> OptimizerState<'a> {
false, false,
false, false,
Position::NONE, Position::NONE,
0,
) )
.ok() .ok()
.map(|(v, _)| v) .map(|(v, _)| v)
@ -462,13 +463,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => { Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
state.set_dirty(); state.set_dirty();
let pos = condition.position(); let pos = condition.start_position();
let mut expr = mem::take(condition); let mut expr = mem::take(condition);
optimize_expr(&mut expr, state, false); optimize_expr(&mut expr, state, false);
*stmt = if preserve_result { *stmt = if preserve_result {
// -> { expr, Noop } // -> { expr, Noop }
Stmt::Block([Stmt::Expr(expr), Stmt::Noop(pos)].into(), pos) Stmt::Block(
[Stmt::Expr(expr), Stmt::Noop(pos)].into(),
(pos, Position::NONE),
)
} else { } else {
// -> expr // -> expr
Stmt::Expr(expr) Stmt::Expr(expr)
@ -486,7 +490,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false) match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
{ {
statements if statements.is_empty() => Stmt::Noop(x.1.position()), statements if statements.is_empty() => Stmt::Noop(x.1.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()), statements => Stmt::Block(statements.into_boxed_slice(), x.1.positions()),
} }
} }
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
@ -496,7 +500,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false) match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false)
{ {
statements if statements.is_empty() => Stmt::Noop(x.0.position()), statements if statements.is_empty() => Stmt::Noop(x.0.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()), statements => Stmt::Block(statements.into_boxed_slice(), x.0.positions()),
} }
} }
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
@ -530,11 +534,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
mem::take(&mut block.statements), mem::take(&mut block.statements),
Stmt::Block( Stmt::Block(
def_stmt.into_boxed_slice(), def_stmt.into_boxed_slice(),
x.def_case.position().or_else(*pos), x.def_case.positions_or_else(*pos, Position::NONE),
) )
.into(), .into(),
)), )),
match_expr.position(), match_expr.start_position(),
); );
} else { } else {
// Promote the matched case // Promote the matched case
@ -545,7 +549,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
true, true,
false, false,
); );
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.position()); *stmt =
Stmt::Block(statements.into_boxed_slice(), block.statements.positions());
} }
state.set_dirty(); state.set_dirty();
@ -585,11 +590,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
mem::take(&mut block.statements), mem::take(&mut block.statements),
Stmt::Block( Stmt::Block(
def_stmt.into_boxed_slice(), def_stmt.into_boxed_slice(),
x.def_case.position().or_else(*pos), x.def_case.positions_or_else(*pos, Position::NONE),
) )
.into(), .into(),
)), )),
match_expr.position(), match_expr.start_position(),
); );
} else { } else {
// Promote the matched case // Promote the matched case
@ -598,7 +603,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
optimize_stmt_block(statements, state, true, true, false); optimize_stmt_block(statements, state, true, true, false);
*stmt = Stmt::Block( *stmt = Stmt::Block(
statements.into_boxed_slice(), statements.into_boxed_slice(),
block.statements.position(), block.statements.positions(),
); );
} }
@ -646,7 +651,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false); optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
*stmt = Stmt::Block( *stmt = Stmt::Block(
def_stmt.into_boxed_slice(), def_stmt.into_boxed_slice(),
x.def_case.position().or_else(*pos), x.def_case.positions_or_else(*pos, Position::NONE),
); );
} }
// switch // switch
@ -703,7 +708,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if preserve_result { if preserve_result {
statements.push(Stmt::Noop(pos)) statements.push(Stmt::Noop(pos))
} }
*stmt = Stmt::Block(statements.into_boxed_slice(), pos); *stmt =
Stmt::Block(statements.into_boxed_slice(), (pos, Position::NONE));
} else { } else {
*stmt = Stmt::Noop(pos); *stmt = Stmt::Noop(pos);
}; };
@ -720,7 +726,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
*stmt = Stmt::Block( *stmt = Stmt::Block(
optimize_stmt_block(mem::take(&mut **body), state, false, true, false) optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
.into_boxed_slice(), .into_boxed_slice(),
body.position(), body.positions(),
); );
} }
// do { block } while|until expr // do { block } while|until expr
@ -748,7 +754,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match block.as_mut_slice() { match block.as_mut_slice() {
[] => { [] => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(*pos); *stmt = Stmt::Noop(pos.0);
} }
// Only one statement which is not block-dependent - promote // Only one statement which is not block-dependent - promote
[s] if !s.is_block_dependent() => { [s] if !s.is_block_dependent() => {
@ -765,7 +771,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
*stmt = Stmt::Block( *stmt = Stmt::Block(
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false) optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
.into_boxed_slice(), .into_boxed_slice(),
x.try_block.position(), x.try_block.positions(),
); );
} }
// try { try_block } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
@ -1072,7 +1078,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
if let Some(value) = arg.get_literal_value() { if let Some(value) = arg.get_literal_value() {
state.set_dirty(); state.set_dirty();
constants.push(value); constants.push(value);
*arg = Expr::Stack(constants.len()-1, arg.position()); *arg = Expr::Stack(constants.len()-1, arg.start_position());
} }
}); });
} }
@ -1120,7 +1126,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
if let Some(value) = arg.get_literal_value() { if let Some(value) = arg.get_literal_value() {
state.set_dirty(); state.set_dirty();
x.constants.push(value); x.constants.push(value);
*arg = Expr::Stack(x.constants.len()-1, arg.position()); *arg = Expr::Stack(x.constants.len()-1, arg.start_position());
} }
}, },
@ -1211,9 +1217,8 @@ pub fn optimize_into_ast(
access: fn_def.access, access: fn_def.access,
body: crate::ast::StmtBlock::NONE, body: crate::ast::StmtBlock::NONE,
params: fn_def.params.clone(), params: fn_def.params.clone(),
lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
global: None, environ: None,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: None, comments: None,

View File

@ -27,15 +27,23 @@ def_package! {
#[export_module] #[export_module]
mod debugging_functions { mod debugging_functions {
/// Get an array of object maps containing the function calls stack.
///
/// If there is no debugging interface registered, an empty array is returned.
///
/// An array of strings is returned under `no_object`.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn stack_trace(ctx: NativeCallContext) -> Array { pub fn back_trace(ctx: NativeCallContext) -> Array {
if let Some(global) = ctx.global_runtime_state() { if let Some(global) = ctx.global_runtime_state() {
global global
.debugger .debugger
.call_stack() .call_stack()
.iter() .iter()
.rev() .rev()
.filter(|crate::debugger::CallStackFrame { fn_name, args, .. }| {
fn_name != "back_trace" || !args.is_empty()
})
.map( .map(
|frame @ crate::debugger::CallStackFrame { |frame @ crate::debugger::CallStackFrame {
fn_name: _fn_name, fn_name: _fn_name,

View File

@ -52,11 +52,11 @@ pub struct ParseState<'e> {
pub entry_stack_len: usize, pub entry_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
pub external_vars: BTreeMap<Identifier, Position>, pub external_vars: Vec<crate::ast::Ident>,
/// An indicator that disables variable capturing into externals one single time /// An indicator that disables variable capturing into externals one single time
/// up until the nearest consumed Identifier token. /// up until the nearest consumed Identifier token.
/// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable. /// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable.
/// All consequent calls to [`access_var`][ParseState::access_var] will not be affected /// All consequent calls to [`access_var`][ParseState::access_var] will not be affected.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
pub allow_capture: bool, pub allow_capture: bool,
/// Encapsulates a local stack with imported [module][crate::Module] names. /// Encapsulates a local stack with imported [module][crate::Module] names.
@ -85,7 +85,7 @@ impl<'e> ParseState<'e> {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()), max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
external_vars: BTreeMap::new(), external_vars: Vec::new(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
allow_capture: true, allow_capture: true,
interned_strings: StringsInterner::new(), interned_strings: StringsInterner::new(),
@ -128,8 +128,11 @@ impl<'e> ParseState<'e> {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
if self.allow_capture { if self.allow_capture {
if index.is_none() && !self.external_vars.contains_key(name) { if index.is_none() && !self.external_vars.iter().any(|v| v.name == name) {
self.external_vars.insert(name.into(), _pos); self.external_vars.push(crate::ast::Ident {
name: name.into(),
pos: _pos,
});
} }
} else { } else {
self.allow_capture = true self.allow_capture = true
@ -303,7 +306,7 @@ impl Expr {
Err( Err(
PERR::MismatchedType("a boolean expression".to_string(), type_name.to_string()) PERR::MismatchedType("a boolean expression".to_string(), type_name.to_string())
.into_err(self.position()), .into_err(self.start_position()),
) )
} }
/// Raise an error if the expression can never yield an iterable value. /// Raise an error if the expression can never yield an iterable value.
@ -323,7 +326,7 @@ impl Expr {
Err( Err(
PERR::MismatchedType("an iterable value".to_string(), type_name.to_string()) PERR::MismatchedType("an iterable value".to_string(), type_name.to_string())
.into_err(self.position()), .into_err(self.start_position()),
) )
} }
} }
@ -518,6 +521,7 @@ fn parse_fn_call(
namespace, namespace,
hashes, hashes,
args, args,
pos: settings.pos,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(settings.pos)); .into_fn_call_expr(settings.pos));
@ -582,6 +586,7 @@ fn parse_fn_call(
namespace, namespace,
hashes, hashes,
args, args,
pos: settings.pos,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(settings.pos)); .into_fn_call_expr(settings.pos));
@ -649,7 +654,7 @@ fn parse_index_chain(
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(lhs.position())) .into_err(lhs.start_position()))
} }
Expr::CharConstant(_, _) Expr::CharConstant(_, _)
@ -660,7 +665,7 @@ fn parse_index_chain(
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(lhs.position())) .into_err(lhs.start_position()))
} }
_ => (), _ => (),
@ -674,7 +679,7 @@ fn parse_index_chain(
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array or string expects numeric index, not a string".into(), "Array or string expects numeric index, not a string".into(),
) )
.into_err(idx_expr.position())) .into_err(idx_expr.start_position()))
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -682,7 +687,7 @@ fn parse_index_chain(
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(lhs.position())) .into_err(lhs.start_position()))
} }
Expr::CharConstant(_, _) Expr::CharConstant(_, _)
@ -693,7 +698,7 @@ fn parse_index_chain(
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(lhs.position())) .into_err(lhs.start_position()))
} }
_ => (), _ => (),
@ -705,35 +710,35 @@ fn parse_index_chain(
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a float".into(), "Array access expects integer index, not a float".into(),
) )
.into_err(x.position())) .into_err(x.start_position()))
} }
// lhs[char] // lhs[char]
x @ Expr::CharConstant(_, _) => { x @ Expr::CharConstant(_, _) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a character".into(), "Array access expects integer index, not a character".into(),
) )
.into_err(x.position())) .into_err(x.start_position()))
} }
// lhs[()] // lhs[()]
x @ Expr::Unit(_) => { x @ Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not ()".into(), "Array access expects integer index, not ()".into(),
) )
.into_err(x.position())) .into_err(x.start_position()))
} }
// lhs[??? && ???], lhs[??? || ???] // lhs[??? && ???], lhs[??? || ???]
x @ Expr::And(_, _) | x @ Expr::Or(_, _) => { x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(), "Array access expects integer index, not a boolean".into(),
) )
.into_err(x.position())) .into_err(x.start_position()))
} }
// lhs[true], lhs[false] // lhs[true], lhs[false]
x @ Expr::BoolConstant(_, _) => { x @ Expr::BoolConstant(_, _) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(), "Array access expects integer index, not a boolean".into(),
) )
.into_err(x.position())) .into_err(x.start_position()))
} }
// All other expressions // All other expressions
_ => (), _ => (),
@ -1045,7 +1050,7 @@ fn parse_switch(
let (hash, range) = if let Some(expr) = expr { let (hash, range) = if let Some(expr) = expr {
let value = expr.get_literal_value().ok_or_else(|| { let value = expr.get_literal_value().ok_or_else(|| {
PERR::ExprExpected("a literal".to_string()).into_err(expr.position()) PERR::ExprExpected("a literal".to_string()).into_err(expr.start_position())
})?; })?;
let guard = value.read_lock::<ExclusiveRange>(); let guard = value.read_lock::<ExclusiveRange>();
@ -1055,14 +1060,14 @@ fn parse_switch(
} else if let Some(range) = value.read_lock::<InclusiveRange>() { } else if let Some(range) = value.read_lock::<InclusiveRange>() {
(None, Some((*range.start(), *range.end(), true))) (None, Some((*range.start(), *range.end(), true)))
} else if value.is::<INT>() && !ranges.is_empty() { } else if value.is::<INT>() && !ranges.is_empty() {
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.position())); return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position()));
} else { } else {
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
if cases.contains_key(&hash) { if cases.contains_key(&hash) {
return Err(PERR::DuplicatedSwitchCase.into_err(expr.position())); return Err(PERR::DuplicatedSwitchCase.into_err(expr.start_position()));
} }
(Some(hash), None) (Some(hash), None)
} }
@ -1258,12 +1263,14 @@ fn parse_primary(
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
new_state.external_vars.iter().try_for_each( new_state.external_vars.iter().try_for_each(
|(captured_var, &pos)| -> ParseResult<_> { |crate::ast::Ident { name, pos }| -> ParseResult<_> {
let index = state.access_var(captured_var, pos); let index = state.access_var(name, *pos);
if !settings.is_closure && settings.strict_var && index.is_none() { if settings.strict_var && !settings.is_closure && index.is_none() {
// If the parent scope is not inside another capturing closure // If the parent scope is not inside another capturing closure
Err(PERR::VariableUndefined(captured_var.to_string()).into_err(pos)) // then we can conclude that the captured variable doesn't exist.
// Under Strict Variables mode, this is not allowed.
Err(PERR::VariableUndefined(name.to_string()).into_err(*pos))
} else { } else {
Ok(()) Ok(())
} }
@ -1677,6 +1684,7 @@ fn parse_unary(
name: state.get_identifier("", "-"), name: state.get_identifier("", "-"),
hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
args, args,
pos,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1703,6 +1711,7 @@ fn parse_unary(
name: state.get_identifier("", "+"), name: state.get_identifier("", "+"),
hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
args, args,
pos,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1720,6 +1729,7 @@ fn parse_unary(
name: state.get_identifier("", "!"), name: state.get_identifier("", "!"),
hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
args, args,
pos,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1748,7 +1758,7 @@ fn make_assignment_stmt(
} }
Expr::Property(_, _) => None, Expr::Property(_, _) => None,
// Anything other than a property after dotting (e.g. a method call) is not an l-value // Anything other than a property after dotting (e.g. a method call) is not an l-value
ref e => Some(e.position()), ref e => Some(e.start_position()),
}, },
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs { Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"), Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
@ -1757,7 +1767,7 @@ fn make_assignment_stmt(
}, },
Expr::Property(_, _) if parent_is_dot => None, Expr::Property(_, _) if parent_is_dot => None,
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"), Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
e if parent_is_dot => Some(e.position()), e if parent_is_dot => Some(e.start_position()),
_ => None, _ => None,
} }
} }
@ -1767,7 +1777,7 @@ fn make_assignment_stmt(
match lhs { match lhs {
// const_expr = rhs // const_expr = rhs
ref expr if expr.is_constant() => { ref expr if expr.is_constant() => {
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) Err(PERR::AssignmentToConstant("".into()).into_err(lhs.start_position()))
} }
// var (non-indexed) = rhs // var (non-indexed) = rhs
Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment( Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment(
@ -1809,10 +1819,8 @@ fn make_assignment_stmt(
op_pos, op_pos,
)), )),
// expr[???] = rhs, expr.??? = rhs // expr[???] = rhs, expr.??? = rhs
ref expr => { ref expr => Err(PERR::AssignmentToInvalidLHS("".to_string())
Err(PERR::AssignmentToInvalidLHS("".to_string()) .into_err(expr.start_position())),
.into_err(expr.position()))
}
} }
} }
Some(err_pos) => { Some(err_pos) => {
@ -1827,7 +1835,7 @@ fn make_assignment_stmt(
) )
.into_err(op_pos)), .into_err(op_pos)),
// expr = rhs // expr = rhs
_ => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(lhs.position())), _ => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(lhs.start_position())),
} }
} }
@ -1978,7 +1986,7 @@ fn make_dot_expr(
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
} }
// lhs.rhs // lhs.rhs
(_, rhs) => Err(PERR::PropertyExpected.into_err(rhs.position())), (_, rhs) => Err(PERR::PropertyExpected.into_err(rhs.start_position())),
} }
} }
@ -2060,6 +2068,7 @@ fn parse_binary_op(
let op_base = FnCallExpr { let op_base = FnCallExpr {
name: state.get_identifier("", op), name: state.get_identifier("", op),
hashes: FnCallHashes::from_native(hash), hashes: FnCallHashes::from_native(hash),
pos,
..Default::default() ..Default::default()
}; };
@ -2077,7 +2086,10 @@ fn parse_binary_op(
| Token::LessThan | Token::LessThan
| Token::LessThanEqualsTo | Token::LessThanEqualsTo
| Token::GreaterThan | Token::GreaterThan
| Token::GreaterThanEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), | Token::GreaterThanEqualsTo => {
let pos = args[0].start_position();
FnCallExpr { args, ..op_base }.into_fn_call_expr(pos)
}
Token::Or => { Token::Or => {
let rhs = args.pop().unwrap(); let rhs = args.pop().unwrap();
@ -2106,6 +2118,7 @@ fn parse_binary_op(
Token::In => { Token::In => {
// Swap the arguments // Swap the arguments
let current_lhs = args.remove(0); let current_lhs = args.remove(0);
let pos = current_lhs.start_position();
args.push(current_lhs); args.push(current_lhs);
args.shrink_to_fit(); args.shrink_to_fit();
@ -2127,6 +2140,7 @@ fn parse_binary_op(
.map_or(false, Option::is_some) => .map_or(false, Option::is_some) =>
{ {
let hash = calc_fn_hash(&s, 2); let hash = calc_fn_hash(&s, 2);
let pos = args[0].start_position();
FnCallExpr { FnCallExpr {
hashes: if is_valid_function_name(&s) { hashes: if is_valid_function_name(&s) {
@ -2140,7 +2154,10 @@ fn parse_binary_op(
.into_fn_call_expr(pos) .into_fn_call_expr(pos)
} }
_ => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), _ => {
let pos = args[0].start_position();
FnCallExpr { args, ..op_base }.into_fn_call_expr(pos)
}
}; };
} }
} }
@ -2574,6 +2591,12 @@ fn parse_let(
// let name ... // let name ...
let (name, pos) = parse_var_name(input)?; let (name, pos) = parse_var_name(input)?;
if !settings.default_options.allow_shadowing
&& state.stack.iter().any(|(v, _)| v == name.as_ref())
{
return Err(PERR::VariableExists(name.to_string()).into_err(pos));
}
let name = state.get_identifier("", name); let name = state.get_identifier("", name);
let var_def = Ident { let var_def = Ident {
name: name.clone(), name: name.clone(),
@ -2729,13 +2752,10 @@ fn parse_block(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let orig_imports_len = state.imports.len(); let orig_imports_len = state.imports.len();
loop { let end_pos = loop {
// Terminated? // Terminated?
match input.peek().expect(NEVER_ENDS) { match input.peek().expect(NEVER_ENDS) {
(Token::RightBrace, _) => { (Token::RightBrace, _) => break eat_token(input, Token::RightBrace),
eat_token(input, Token::RightBrace);
break;
}
(Token::EOF, pos) => { (Token::EOF, pos) => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
Token::RightBrace.into(), Token::RightBrace.into(),
@ -2762,10 +2782,7 @@ fn parse_block(
match input.peek().expect(NEVER_ENDS) { match input.peek().expect(NEVER_ENDS) {
// { ... stmt } // { ... stmt }
(Token::RightBrace, _) => { (Token::RightBrace, _) => break eat_token(input, Token::RightBrace),
eat_token(input, Token::RightBrace);
break;
}
// { ... stmt; // { ... stmt;
(Token::SemiColon, _) if need_semicolon => { (Token::SemiColon, _) if need_semicolon => {
eat_token(input, Token::SemiColon); eat_token(input, Token::SemiColon);
@ -2788,7 +2805,7 @@ fn parse_block(
.into_err(*pos)); .into_err(*pos));
} }
} }
} };
state.stack.truncate(state.entry_stack_len); state.stack.truncate(state.entry_stack_len);
state.entry_stack_len = prev_entry_stack_len; state.entry_stack_len = prev_entry_stack_len;
@ -2796,7 +2813,10 @@ fn parse_block(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
state.imports.truncate(orig_imports_len); state.imports.truncate(orig_imports_len);
Ok(Stmt::Block(statements.into_boxed_slice(), settings.pos)) Ok(Stmt::Block(
statements.into_boxed_slice(),
(settings.pos, end_pos),
))
} }
/// Parse an expression as a statement. /// Parse an expression as a statement.
@ -2959,25 +2979,25 @@ fn parse_stmt(
Token::If => parse_if(input, state, lib, settings.level_up()), Token::If => parse_if(input, state, lib, settings.level_up()),
Token::Switch => parse_switch(input, state, lib, settings.level_up()), Token::Switch => parse_switch(input, state, lib, settings.level_up()),
Token::While | Token::Loop if settings.default_options.allow_loop => { Token::While | Token::Loop if settings.default_options.allow_looping => {
parse_while_loop(input, state, lib, settings.level_up()) parse_while_loop(input, state, lib, settings.level_up())
} }
Token::Do if settings.default_options.allow_loop => { Token::Do if settings.default_options.allow_looping => {
parse_do(input, state, lib, settings.level_up()) parse_do(input, state, lib, settings.level_up())
} }
Token::For if settings.default_options.allow_loop => { Token::For if settings.default_options.allow_looping => {
parse_for(input, state, lib, settings.level_up()) parse_for(input, state, lib, settings.level_up())
} }
Token::Continue if settings.default_options.allow_loop && settings.is_breakable => { Token::Continue if settings.default_options.allow_looping && settings.is_breakable => {
let pos = eat_token(input, Token::Continue); let pos = eat_token(input, Token::Continue);
Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos)) Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos))
} }
Token::Break if settings.default_options.allow_loop && settings.is_breakable => { Token::Break if settings.default_options.allow_looping && settings.is_breakable => {
let pos = eat_token(input, Token::Break); let pos = eat_token(input, Token::Break);
Ok(Stmt::BreakLoop(AST_OPTION_BREAK, pos)) Ok(Stmt::BreakLoop(AST_OPTION_BREAK, pos))
} }
Token::Continue | Token::Break if settings.default_options.allow_loop => { Token::Continue | Token::Break if settings.default_options.allow_looping => {
Err(PERR::LoopBreak.into_err(token_pos)) Err(PERR::LoopBreak.into_err(token_pos))
} }
@ -3185,9 +3205,8 @@ fn parse_fn(
access, access,
params, params,
body, body,
lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
global: None, environ: None,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: if comments.is_empty() { comments: if comments.is_empty() {
@ -3240,6 +3259,7 @@ fn make_curry_from_externals(
num_externals + 1, num_externals + 1,
)), )),
args, args,
pos,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(pos); .into_fn_call_expr(pos);
@ -3249,7 +3269,7 @@ fn make_curry_from_externals(
let mut statements = StaticVec::with_capacity(externals.len() + 1); let mut statements = StaticVec::with_capacity(externals.len() + 1);
statements.extend(externals.into_iter().map(Stmt::Share)); statements.extend(externals.into_iter().map(Stmt::Share));
statements.push(Stmt::Expr(expr)); statements.push(Stmt::Expr(expr));
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos).into()) Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
} }
/// Parse an anonymous function definition. /// Parse an anonymous function definition.
@ -3312,20 +3332,21 @@ fn parse_anon_fn(
// External variables may need to be processed in a consistent order, // External variables may need to be processed in a consistent order,
// so extract them into a list. // so extract them into a list.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let externals: StaticVec<Identifier> = state let (mut params, externals) = {
.external_vars let externals: StaticVec<Identifier> = state
.iter() .external_vars
.map(|(name, _)| name.clone()) .iter()
.collect(); .map(|crate::ast::Ident { name, .. }| name.clone())
.collect();
#[cfg(not(feature = "no_closure"))] let mut params = StaticVec::with_capacity(params_list.len() + externals.len());
let mut params = StaticVec::with_capacity(params_list.len() + externals.len()); params.extend(externals.iter().cloned());
(params, externals)
};
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
let mut params = StaticVec::with_capacity(params_list.len()); let mut params = StaticVec::with_capacity(params_list.len());
#[cfg(not(feature = "no_closure"))]
params.extend(externals.iter().cloned());
params.append(&mut params_list); params.append(&mut params_list);
// Create unique function name by hashing the script body plus the parameters. // Create unique function name by hashing the script body plus the parameters.
@ -3341,9 +3362,8 @@ fn parse_anon_fn(
access: crate::FnAccess::Public, access: crate::FnAccess::Public,
params, params,
body: body.into(), body: body.into(),
lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
global: None, environ: None,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: None, comments: None,

View File

@ -37,9 +37,9 @@ fn check_struct_sizes() {
assert_eq!( assert_eq!(
size_of::<NativeCallContext>(), size_of::<NativeCallContext>(),
if cfg!(feature = "no_position") { if cfg!(feature = "no_position") {
64
} else {
72 72
} else {
80
} }
); );
} }

View File

@ -16,11 +16,11 @@ use std::{
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
use std::time::Instant; pub use std::time::Instant;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
use instant::Instant; pub use instant::Instant;
/// The message: data type was checked /// The message: data type was checked
const CHECKED: &str = "data type was checked"; const CHECKED: &str = "data type was checked";
@ -543,74 +543,6 @@ impl Hash for Dynamic {
} }
} }
/// Map the name of a standard type into a friendly form.
#[inline]
#[must_use]
pub(crate) fn map_std_type_name(name: &str, shorthands: bool) -> &str {
let name = name.trim();
if name.starts_with("rhai::") {
return map_std_type_name(&name[6..], shorthands);
}
if name == type_name::<String>() {
return if shorthands { "string" } else { "String" };
}
if name == type_name::<ImmutableString>() {
return if shorthands {
"string"
} else {
"ImmutableString"
};
}
if name == type_name::<&str>() {
return if shorthands { "string" } else { "&str" };
}
#[cfg(feature = "decimal")]
if name == type_name::<rust_decimal::Decimal>() {
return if shorthands { "decimal" } else { "Decimal" };
}
if name == type_name::<FnPtr>() {
return if shorthands { "Fn" } else { "FnPtr" };
}
#[cfg(not(feature = "no_index"))]
if name == type_name::<crate::Array>() {
return if shorthands { "array" } else { "Array" };
}
#[cfg(not(feature = "no_index"))]
if name == type_name::<crate::Blob>() {
return if shorthands { "blob" } else { "Blob" };
}
#[cfg(not(feature = "no_object"))]
if name == type_name::<crate::Map>() {
return if shorthands { "map" } else { "Map" };
}
#[cfg(not(feature = "no_std"))]
if name == type_name::<Instant>() {
return if shorthands { "timestamp" } else { "Instant" };
}
if name == type_name::<ExclusiveRange>() || name == "ExclusiveRange" {
return if shorthands {
"range"
} else if cfg!(feature = "only_i32") {
"Range<i32>"
} else {
"Range<i64>"
};
}
if name == type_name::<InclusiveRange>() || name == "InclusiveRange" {
return if shorthands {
"range="
} else if cfg!(feature = "only_i32") {
"RangeInclusive<i32>"
} else {
"RangeInclusive<i64>"
};
}
name
}
impl fmt::Display for Dynamic { impl fmt::Display for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 { match self.0 {

View File

@ -30,6 +30,8 @@ pub enum EvalAltResult {
/// Syntax error. /// Syntax error.
ErrorParsing(ParseErrorType, Position), ErrorParsing(ParseErrorType, Position),
/// Shadowing of an existing variable disallowed. Wrapped value is the variable name.
ErrorVariableExists(String, Position),
/// Usage of an unknown variable. Wrapped value is the variable name. /// Usage of an unknown variable. Wrapped value is the variable name.
ErrorVariableNotFound(String, Position), ErrorVariableNotFound(String, Position),
/// Call to an unknown function. Wrapped value is the function signature. /// Call to an unknown function. Wrapped value is the function signature.
@ -139,8 +141,9 @@ impl fmt::Display for EvalAltResult {
} }
Self::ErrorInModule(s, err, _) => write!(f, "Error in module {}: {}", s, err)?, Self::ErrorInModule(s, err, _) => write!(f, "Error in module {}: {}", s, err)?,
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?, Self::ErrorVariableExists(s, _) => write!(f, "Variable is already defined: {}", s)?,
Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?, Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?, Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?,
Self::ErrorDataRace(s, _) => { Self::ErrorDataRace(s, _) => {
write!(f, "Data race detected when accessing variable: {}", s)? write!(f, "Data race detected when accessing variable: {}", s)?
@ -149,7 +152,7 @@ impl fmt::Display for EvalAltResult {
"" => f.write_str("Malformed dot expression"), "" => f.write_str("Malformed dot expression"),
s => f.write_str(s), s => f.write_str(s),
}?, }?,
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for {}", s)?, Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered: {}", s)?,
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?, Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
Self::ErrorFor(_) => f.write_str("For loop expects a type that is iterable")?, Self::ErrorFor(_) => f.write_str("For loop expects a type that is iterable")?,
Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?, Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
@ -166,7 +169,7 @@ impl fmt::Display for EvalAltResult {
} }
Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?, Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?,
Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant {}", s)?, Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant: {}", s)?,
Self::ErrorMismatchOutputType(s, r, _) => match (r.as_str(), s.as_str()) { Self::ErrorMismatchOutputType(s, r, _) => match (r.as_str(), s.as_str()) {
("", s) => write!(f, "Output type is incorrect, expecting {}", s), ("", s) => write!(f, "Output type is incorrect, expecting {}", s),
(r, "") => write!(f, "Output type is incorrect: {}", r), (r, "") => write!(f, "Output type is incorrect: {}", r),
@ -274,6 +277,7 @@ impl EvalAltResult {
| Self::ErrorBitFieldBounds(_, _, _) | Self::ErrorBitFieldBounds(_, _, _)
| Self::ErrorIndexingType(_, _) | Self::ErrorIndexingType(_, _)
| Self::ErrorFor(_) | Self::ErrorFor(_)
| Self::ErrorVariableExists(_, _)
| Self::ErrorVariableNotFound(_, _) | Self::ErrorVariableNotFound(_, _)
| Self::ErrorModuleNotFound(_, _) | Self::ErrorModuleNotFound(_, _)
| Self::ErrorDataRace(_, _) | Self::ErrorDataRace(_, _)
@ -364,7 +368,8 @@ impl EvalAltResult {
Self::ErrorIndexingType(t, _) => { Self::ErrorIndexingType(t, _) => {
map.insert("type".into(), t.into()); map.insert("type".into(), t.into());
} }
Self::ErrorVariableNotFound(v, _) Self::ErrorVariableExists(v, _)
| Self::ErrorVariableNotFound(v, _)
| Self::ErrorDataRace(v, _) | Self::ErrorDataRace(v, _)
| Self::ErrorAssignmentToConstant(v, _) => { | Self::ErrorAssignmentToConstant(v, _) => {
map.insert("variable".into(), v.into()); map.insert("variable".into(), v.into());
@ -415,6 +420,7 @@ impl EvalAltResult {
| Self::ErrorBitFieldBounds(_, _, pos) | Self::ErrorBitFieldBounds(_, _, pos)
| Self::ErrorIndexingType(_, pos) | Self::ErrorIndexingType(_, pos)
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorVariableExists(_, pos)
| Self::ErrorVariableNotFound(_, pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorModuleNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos)
| Self::ErrorDataRace(_, pos) | Self::ErrorDataRace(_, pos)
@ -463,6 +469,7 @@ impl EvalAltResult {
| Self::ErrorBitFieldBounds(_, _, pos) | Self::ErrorBitFieldBounds(_, _, pos)
| Self::ErrorIndexingType(_, pos) | Self::ErrorIndexingType(_, pos)
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorVariableExists(_, pos)
| Self::ErrorVariableNotFound(_, pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorModuleNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos)
| Self::ErrorDataRace(_, pos) | Self::ErrorDataRace(_, pos)

View File

@ -9,6 +9,8 @@ pub mod parse_error;
pub mod scope; pub mod scope;
pub use dynamic::Dynamic; pub use dynamic::Dynamic;
#[cfg(not(feature = "no_std"))]
pub use dynamic::Instant;
pub use error::EvalAltResult; pub use error::EvalAltResult;
pub use fn_ptr::FnPtr; pub use fn_ptr::FnPtr;
pub use immutable_string::ImmutableString; pub use immutable_string::ImmutableString;

View File

@ -167,6 +167,10 @@ pub enum ParseErrorType {
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// Assignment to an inappropriate LHS (left-hand-side) expression.
/// Wrapped value is the error message (if any). /// Wrapped value is the error message (if any).
AssignmentToInvalidLHS(String), AssignmentToInvalidLHS(String),
/// A variable is already defined.
///
/// Only appears when variables shadowing is disabled.
VariableExists(String),
/// A variable is not found. /// A variable is not found.
/// ///
/// Only appears when strict variables mode is enabled. /// Only appears when strict variables mode is enabled.
@ -241,6 +245,7 @@ impl fmt::Display for ParseErrorType {
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"), Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s), Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s),
Self::VariableExists(s) => write!(f, "Variable already defined: {}", s),
Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s), Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s),
Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s), Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s),

View File

@ -9,7 +9,12 @@ use rhai::Map;
#[test] #[test]
fn test_debugging() -> Result<(), Box<EvalAltResult>> { fn test_debugging() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let mut engine = Engine::new();
engine.register_debugger(
|| Dynamic::UNIT,
|_, _, _, _, _| Ok(rhai::debugger::DebuggerCommand::Continue),
);
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -18,7 +23,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
" "
fn foo(x) { fn foo(x) {
if x >= 5 { if x >= 5 {
stack_trace() back_trace()
} else { } else {
foo(x+1) foo(x+1)
} }
@ -30,7 +35,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
assert_eq!(r.len(), 6); assert_eq!(r.len(), 6);
assert_eq!(engine.eval::<INT>("len(stack_trace())")?, 0); assert_eq!(engine.eval::<INT>("len(back_trace())")?, 0);
} }
Ok(()) Ok(())
@ -41,7 +46,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
fn test_debugger_state() -> Result<(), Box<EvalAltResult>> { fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.on_debugger( engine.register_debugger(
|| { || {
// Say, use an object map for the debugger state // Say, use an object map for the debugger state
let mut state = Map::new(); let mut state = Map::new();
@ -50,7 +55,7 @@ fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
state.insert("foo".into(), false.into()); state.insert("foo".into(), false.into());
Dynamic::from_map(state) Dynamic::from_map(state)
}, },
|context, _, _, _| { |context, _, _, _, _| {
// Get global runtime state // Get global runtime state
let global = context.global_runtime_state_mut(); let global = context.global_runtime_state_mut();

View File

@ -514,3 +514,47 @@ fn test_module_file() -> Result<(), Box<EvalAltResult>> {
Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_function"))]
#[test]
fn test_module_environ() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let ast = engine.compile(
r#"
const SECRET = 42;
fn foo(x) {
print(global::SECRET);
global::SECRET + x
}
"#,
)?;
let mut m = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
m.set_id("test");
m.build_index();
engine.register_static_module("test", m.into());
assert_eq!(
engine.eval::<String>(
r#"
const SECRET = "hello";
fn foo(x) {
print(global::SECRET);
global::SECRET + x
}
let t = test::foo(0);
foo(t)
"#
)?,
"hello42"
);
Ok(())
}

View File

@ -78,33 +78,24 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
assert_eq!( assert_eq!(format!("{:?}", ast), "AST { body: [Expr(123 @ 1:53)] }");
format!("{:?}", ast),
"AST { source: \"\", body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }"
);
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{:?}", ast),
r#"AST { source: "", body: Block[Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# r#"AST { body: [Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)] }"#
); );
let ast = engine.compile("if 1 == 2 { 42 }")?; let ast = engine.compile("if 1 == 2 { 42 }")?;
assert_eq!( assert_eq!(format!("{:?}", ast), "AST { body: [] }");
format!("{:?}", ast),
"AST { source: \"\", body: Block[], functions: Module, resolver: None }"
);
engine.set_optimization_level(OptimizationLevel::Full); engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile("abs(-42)")?; let ast = engine.compile("abs(-42)")?;
assert_eq!( assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }");
format!("{:?}", ast),
"AST { source: \"\", body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }"
);
Ok(()) Ok(())
} }

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult, Scope, INT};
#[test] #[test]
fn test_options_allow() -> Result<(), Box<EvalAltResult>> { fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
@ -31,6 +31,20 @@ fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
assert!(engine.compile("while x > y { foo(z); }").is_err()); assert!(engine.compile("while x > y { foo(z); }").is_err());
engine.compile("let x = 42; let x = 123;")?;
engine.set_allow_shadowing(false);
assert!(engine.compile("let x = 42; let x = 123;").is_err());
assert!(engine.compile("const x = 42; let x = 123;").is_err());
assert!(engine.compile("let x = 42; const x = 123;").is_err());
assert!(engine.compile("const x = 42; const x = 123;").is_err());
let mut scope = Scope::new();
scope.push("x", 42 as INT);
assert!(engine.run_with_scope(&mut scope, "let x = 42;").is_err());
Ok(()) Ok(())
} }

View File

@ -120,3 +120,26 @@ fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.on_def_var(|name, _, scope_level, _, _| match (name, scope_level) {
("x", 0 | 1) => Ok(false),
_ => Ok(true),
});
assert_eq!(
engine.eval::<INT>("let y = 42; let y = 123; let z = y + 1; z")?,
124
);
assert!(engine.run("let x = 42;").is_err());
assert!(engine.run("const x = 42;").is_err());
assert!(engine.run("let y = 42; { let x = y + 1; }").is_err());
assert!(engine.run("let y = 42; { let x = y + 1; }").is_err());
engine.run("let y = 42; { let z = y + 1; { let x = z + 1; } }")?;
Ok(())
}