Merge pull request #518 from schungx/master
Add rustyline to rhai-repl and variable definition filter.
This commit is contained in:
commit
c7888ba1f4
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- run: rustup toolchain update nightly && rustup default nightly
|
||||
- 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
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
|
40
CHANGELOG.md
40
CHANGELOG.md
@ -12,25 +12,57 @@ Bug fixes
|
||||
* In `Scope::clone_visible`, constants are now properly cloned as constants.
|
||||
* Variables introduced inside `try` blocks are now properly cleaned up upon an exception.
|
||||
* Off-by-one error in character positions after a comment line is now fixed.
|
||||
* 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
|
||||
-----------------------
|
||||
|
||||
* 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
|
||||
------------
|
||||
|
||||
* A debugging interface is added.
|
||||
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
||||
* A new package, `DebuggingPackage`, is added which contains the `stack_trace` function to get the current call stack anywhere in a script.
|
||||
* 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
|
||||
------------
|
||||
|
||||
* `rhai-repl` tool has a few more commands, such as `strict` to turn on/off _Strict Variables Mode_ and `optimize` to turn on/off script optimization.
|
||||
* Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required.
|
||||
* The `no_module` feature now eliminates large sections of code via feature gates.
|
||||
* 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
|
||||
@ -551,7 +583,7 @@ Breaking changes
|
||||
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.
|
||||
|
||||
Enhancements
|
||||
|
@ -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
|
||||
debugging = ["internals"] # enable debugging
|
||||
|
||||
# compiling for no-std
|
||||
no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"]
|
||||
|
||||
# compiling for WASM
|
||||
wasm-bindgen = ["instant/wasm-bindgen"]
|
||||
stdweb = ["instant/stdweb"]
|
||||
|
||||
# compiling bin tools
|
||||
bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"]
|
||||
|
||||
[[bin]]
|
||||
name = "rhai-repl"
|
||||
required-features = ["rustyline"]
|
||||
|
@ -190,8 +190,8 @@ impl Engine {
|
||||
&mut this_ptr,
|
||||
fn_def,
|
||||
&mut args,
|
||||
Position::NONE,
|
||||
rewind_scope,
|
||||
Position::NONE,
|
||||
0,
|
||||
);
|
||||
|
||||
|
@ -15,12 +15,12 @@ impl Engine {
|
||||
/// > `Fn(name: &str, index: usize, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>`
|
||||
///
|
||||
/// where:
|
||||
/// * `name`: name of the variable.
|
||||
/// * `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
|
||||
/// 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
|
||||
/// and a search through the current [`Scope`][crate::Scope] must be performed.
|
||||
///
|
||||
/// * `context`: the current [evaluation context][`EvalContext`].
|
||||
///
|
||||
/// ## Return value
|
||||
@ -63,6 +63,67 @@ impl Engine {
|
||||
self.resolve_var = Some(Box::new(callback));
|
||||
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.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
@ -264,11 +325,12 @@ impl Engine {
|
||||
/// Exported under the `debugging` feature only.
|
||||
#[cfg(feature = "debugging")]
|
||||
#[inline(always)]
|
||||
pub fn on_debugger(
|
||||
pub fn register_debugger(
|
||||
&mut self,
|
||||
init: impl Fn() -> Dynamic + SendSync + 'static,
|
||||
callback: impl Fn(
|
||||
&mut EvalContext,
|
||||
crate::eval::DebuggerEvent,
|
||||
crate::ast::ASTNode,
|
||||
Option<&str>,
|
||||
Position,
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Module defining the public API of the Rhai engine.
|
||||
|
||||
pub mod type_names;
|
||||
|
||||
pub mod eval;
|
||||
|
||||
pub mod run;
|
||||
|
@ -17,9 +17,11 @@ pub struct LanguageOptions {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub allow_anonymous_fn: bool,
|
||||
/// Is looping allowed?
|
||||
pub allow_loop: bool,
|
||||
pub allow_looping: bool,
|
||||
/// Strict variables mode?
|
||||
pub strict_var: bool,
|
||||
/// Is variables shadowing allowed?
|
||||
pub allow_shadowing: bool,
|
||||
}
|
||||
|
||||
impl LanguageOptions {
|
||||
@ -32,8 +34,9 @@ impl LanguageOptions {
|
||||
allow_stmt_expr: true,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
allow_anonymous_fn: true,
|
||||
allow_loop: true,
|
||||
allow_looping: true,
|
||||
strict_var: false,
|
||||
allow_shadowing: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,12 +97,12 @@ impl Engine {
|
||||
/// Is looping allowed?
|
||||
#[inline(always)]
|
||||
pub fn allow_looping(&self) -> bool {
|
||||
self.options.allow_loop
|
||||
self.options.allow_looping
|
||||
}
|
||||
/// Set whether looping is allowed.
|
||||
#[inline(always)]
|
||||
pub fn set_allow_looping(&mut self, enable: bool) {
|
||||
self.options.allow_loop = enable;
|
||||
self.options.allow_looping = enable;
|
||||
}
|
||||
/// Is strict variables mode enabled?
|
||||
#[inline(always)]
|
||||
@ -111,4 +114,14 @@ impl Engine {
|
||||
pub fn set_strict_variables(&mut self, enable: bool) {
|
||||
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
131
src/api/type_names.rs
Normal 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()
|
||||
}
|
||||
}
|
113
src/ast/ast.rs
113
src/ast/ast.rs
@ -1,10 +1,11 @@
|
||||
//! 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};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
fmt,
|
||||
hash::Hash,
|
||||
ops::{Add, AddAssign},
|
||||
};
|
||||
@ -14,7 +15,7 @@ use std::{
|
||||
/// # Thread Safety
|
||||
///
|
||||
/// 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 {
|
||||
/// Source of the [`AST`].
|
||||
/// No source if string is empty.
|
||||
@ -23,7 +24,7 @@ pub struct AST {
|
||||
body: StmtBlock,
|
||||
/// Script-defined functions.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: crate::Shared<crate::Module>,
|
||||
lib: crate::Shared<crate::Module>,
|
||||
/// Embedded module resolver, if any.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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 {
|
||||
/// Create a new [`AST`].
|
||||
#[cfg(not(feature = "internals"))]
|
||||
@ -47,9 +73,9 @@ impl AST {
|
||||
) -> Self {
|
||||
Self {
|
||||
source: Identifier::new_const(),
|
||||
body: StmtBlock::new(statements, Position::NONE),
|
||||
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: functions.into(),
|
||||
lib: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
}
|
||||
@ -65,9 +91,9 @@ impl AST {
|
||||
) -> Self {
|
||||
Self {
|
||||
source: Identifier::new_const(),
|
||||
body: StmtBlock::new(statements, Position::NONE),
|
||||
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: functions.into(),
|
||||
lib: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
}
|
||||
@ -115,7 +141,7 @@ impl AST {
|
||||
source: Identifier::new_const(),
|
||||
body: StmtBlock::NONE,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: crate::Module::new().into(),
|
||||
lib: crate::Module::new().into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
}
|
||||
@ -140,7 +166,7 @@ impl AST {
|
||||
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
|
||||
let source = source.into();
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::Shared::get_mut(&mut self.functions)
|
||||
crate::Shared::get_mut(&mut self.lib)
|
||||
.as_mut()
|
||||
.map(|m| m.set_id(source.clone()));
|
||||
self.source = source;
|
||||
@ -181,7 +207,7 @@ impl AST {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
@ -189,7 +215,7 @@ impl AST {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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.
|
||||
/// Exported under the `internals` feature only.
|
||||
@ -200,7 +226,7 @@ impl AST {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn shared_lib(&self) -> &crate::Shared<crate::Module> {
|
||||
&self.functions
|
||||
&self.lib
|
||||
}
|
||||
/// Get the embedded [module resolver][`ModuleResolver`].
|
||||
#[cfg(not(feature = "internals"))]
|
||||
@ -260,12 +286,12 @@ impl AST {
|
||||
&self,
|
||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> Self {
|
||||
let mut functions = crate::Module::new();
|
||||
functions.merge_filtered(&self.functions, &filter);
|
||||
let mut lib = crate::Module::new();
|
||||
lib.merge_filtered(&self.lib, &filter);
|
||||
Self {
|
||||
source: self.source.clone(),
|
||||
body: StmtBlock::NONE,
|
||||
functions: functions.into(),
|
||||
lib: lib.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: self.resolver.clone(),
|
||||
}
|
||||
@ -279,7 +305,7 @@ impl AST {
|
||||
source: self.source.clone(),
|
||||
body: self.body.clone(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: crate::Module::new().into(),
|
||||
lib: crate::Module::new().into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: self.resolver.clone(),
|
||||
}
|
||||
@ -472,24 +498,24 @@ impl AST {
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let functions = {
|
||||
let mut functions = self.functions.as_ref().clone();
|
||||
functions.merge_filtered(&other.functions, &_filter);
|
||||
functions
|
||||
let lib = {
|
||||
let mut lib = self.lib.as_ref().clone();
|
||||
lib.merge_filtered(&other.lib, &_filter);
|
||||
lib
|
||||
};
|
||||
|
||||
if !other.source.is_empty() {
|
||||
Self::new_with_source(
|
||||
merged,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions,
|
||||
lib,
|
||||
other.source.clone(),
|
||||
)
|
||||
} else {
|
||||
Self::new(
|
||||
merged,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions,
|
||||
lib,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -562,9 +588,9 @@ impl AST {
|
||||
self.body.extend(other.body.into_iter());
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if !other.functions.is_empty() {
|
||||
crate::func::native::shared_make_mut(&mut self.functions)
|
||||
.merge_filtered(&other.functions, &_filter);
|
||||
if !other.lib.is_empty() {
|
||||
crate::func::native::shared_make_mut(&mut self.lib)
|
||||
.merge_filtered(&other.lib, &_filter);
|
||||
}
|
||||
self
|
||||
}
|
||||
@ -599,20 +625,32 @@ impl AST {
|
||||
&mut self,
|
||||
filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
if !self.functions.is_empty() {
|
||||
crate::func::native::shared_make_mut(&mut self.functions)
|
||||
.retain_script_functions(filter);
|
||||
if !self.lib.is_empty() {
|
||||
crate::func::native::shared_make_mut(&mut self.lib).retain_script_functions(filter);
|
||||
}
|
||||
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.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
|
||||
self.functions
|
||||
self.lib
|
||||
.iter_script_fn()
|
||||
.map(|(_, _, _, _, fn_def)| fn_def.as_ref())
|
||||
}
|
||||
@ -622,7 +660,7 @@ impl AST {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
pub fn iter_functions<'a>(&'a self) -> impl Iterator<Item = super::ScriptFnMetadata> + 'a {
|
||||
self.functions
|
||||
self.lib
|
||||
.iter_script_fn()
|
||||
.map(|(_, _, _, _, fn_def)| fn_def.as_ref().into())
|
||||
}
|
||||
@ -632,7 +670,7 @@ impl AST {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub fn clear_functions(&mut self) -> &mut Self {
|
||||
self.functions = crate::Module::new().into();
|
||||
self.lib = crate::Module::new().into();
|
||||
self
|
||||
}
|
||||
/// Clear all statements in the [`AST`], leaving only function definitions.
|
||||
@ -707,16 +745,11 @@ impl AST {
|
||||
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
|
||||
self.statements().iter().filter_map(move |stmt| match stmt {
|
||||
Stmt::Var(expr, name, options, _)
|
||||
if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants
|
||||
|| !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT)
|
||||
&& include_variables =>
|
||||
if options.contains(AST_OPTION_CONSTANT) && include_constants
|
||||
|| !options.contains(AST_OPTION_CONSTANT) && include_variables =>
|
||||
{
|
||||
if let Some(value) = expr.get_literal_value() {
|
||||
Some((
|
||||
name.as_str(),
|
||||
options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT),
|
||||
value,
|
||||
))
|
||||
Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -871,6 +904,6 @@ impl AST {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn lib(&self) -> &crate::Module {
|
||||
&self.functions
|
||||
&self.lib
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ impl FnCallHashes {
|
||||
|
||||
/// _(internals)_ A function call.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug, Clone, Default, Hash)]
|
||||
#[derive(Clone, Default, Hash)]
|
||||
pub struct FnCallExpr {
|
||||
/// Namespace of the function, if any.
|
||||
///
|
||||
@ -191,6 +191,27 @@ pub struct FnCallExpr {
|
||||
pub constants: StaticVec<Dynamic>,
|
||||
/// Does this function call capture the parent scope?
|
||||
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 {
|
||||
@ -419,7 +440,7 @@ impl Default for Expr {
|
||||
|
||||
impl fmt::Debug for Expr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut display_pos = self.position();
|
||||
let mut display_pos = self.start_position();
|
||||
|
||||
match self {
|
||||
Self::DynamicConstant(value, _) => write!(f, "{:?}", value),
|
||||
@ -459,26 +480,12 @@ impl fmt::Debug for Expr {
|
||||
f.write_str(")")
|
||||
}
|
||||
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) => {
|
||||
f.write_str("ExprStmtBlock")?;
|
||||
f.debug_list().entries(x.iter()).finish()
|
||||
}
|
||||
Self::FnCall(x, _) => {
|
||||
let mut ff = f.debug_struct("FnCall");
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
x.namespace.as_ref().map(|ns| ff.field("namespace", ns));
|
||||
ff.field("name", &x.name)
|
||||
.field("hash", &x.hashes)
|
||||
.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::FnCall(x, _) => fmt::Debug::fmt(x, f),
|
||||
Self::Index(x, term, pos) => {
|
||||
display_pos = *pos;
|
||||
|
||||
@ -625,6 +632,7 @@ impl Expr {
|
||||
args: once(Self::Stack(0, pos)).collect(),
|
||||
constants: once(f.fn_name().into()).collect(),
|
||||
capture_parent_scope: false,
|
||||
pos,
|
||||
}
|
||||
.into(),
|
||||
pos,
|
||||
@ -681,15 +689,30 @@ impl Expr {
|
||||
| Self::Map(_, pos)
|
||||
| Self::Variable(_, pos, _)
|
||||
| Self::Stack(_, pos)
|
||||
| Self::FnCall(_, pos)
|
||||
| Self::And(_, pos)
|
||||
| Self::Or(_, pos)
|
||||
| Self::Index(_, _, pos)
|
||||
| Self::Dot(_, _, pos)
|
||||
| Self::Custom(_, pos)
|
||||
| Self::InterpolatedString(_, 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.
|
||||
@ -718,7 +741,7 @@ impl Expr {
|
||||
| Self::InterpolatedString(_, 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
|
||||
|
@ -11,6 +11,9 @@ pub use ast::{ASTNode, AST};
|
||||
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
|
||||
pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
|
||||
pub use ident::Ident;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use script_fn::EncapsulatedEnviron;
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
||||
pub use stmt::{ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, SwitchCases, TryCatchBlock};
|
||||
|
@ -2,24 +2,43 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
|
||||
use super::{FnAccess, StmtBlock};
|
||||
use crate::{Identifier, Module, Shared, StaticVec};
|
||||
use crate::{Identifier, StaticVec};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
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.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScriptFnDef {
|
||||
/// Function body.
|
||||
pub body: StmtBlock,
|
||||
/// Encapsulated running environment, if any.
|
||||
pub lib: Option<Shared<Module>>,
|
||||
/// Encapsulated stack of imported modules, if any.
|
||||
/// Encapsulated AST environment, if any.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
/// Not available under `no_module` or `no_function`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub global: Option<Box<[(Identifier, Shared<Module>)]>>,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub environ: Option<EncapsulatedEnviron>,
|
||||
/// Function name.
|
||||
pub name: Identifier,
|
||||
/// Function access mode.
|
||||
|
@ -134,7 +134,7 @@ pub struct TryCatchBlock {
|
||||
/// _(internals)_ A scoped block of statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Clone, Hash, Default)]
|
||||
pub struct StmtBlock(StaticVec<Stmt>, Position);
|
||||
pub struct StmtBlock(StaticVec<Stmt>, (Position, Position));
|
||||
|
||||
impl StmtBlock {
|
||||
/// A [`StmtBlock`] that does not exist.
|
||||
@ -142,16 +142,20 @@ impl StmtBlock {
|
||||
|
||||
/// Create a new [`StmtBlock`].
|
||||
#[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();
|
||||
statements.shrink_to_fit();
|
||||
Self(statements, pos)
|
||||
Self(statements, (start_pos, end_pos))
|
||||
}
|
||||
/// Create an empty [`StmtBlock`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn empty(pos: Position) -> Self {
|
||||
Self(StaticVec::new_const(), pos)
|
||||
Self(StaticVec::new_const(), (pos, pos))
|
||||
}
|
||||
/// Is this statements block empty?
|
||||
#[inline(always)]
|
||||
@ -183,16 +187,42 @@ impl StmtBlock {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Stmt> {
|
||||
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)]
|
||||
#[must_use]
|
||||
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
|
||||
}
|
||||
/// 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)]
|
||||
pub fn set_position(&mut self, pos: Position) {
|
||||
self.1 = pos;
|
||||
#[must_use]
|
||||
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 {
|
||||
f.write_str("Block")?;
|
||||
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 {
|
||||
match stmt {
|
||||
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();
|
||||
Self(vec![stmt].into(), pos)
|
||||
Self(vec![stmt].into(), (pos, Position::NONE))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,7 +344,7 @@ pub enum Stmt {
|
||||
/// function call forming one statement.
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
/// `{` stmt`;` ... `}`
|
||||
Block(Box<[Stmt]>, Position),
|
||||
Block(Box<[Stmt]>, (Position, Position)),
|
||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||
TryCatch(Box<TryCatchBlock>, Position),
|
||||
/// [expression][Expr]
|
||||
@ -377,7 +412,7 @@ impl Stmt {
|
||||
match self {
|
||||
Self::Noop(pos)
|
||||
| Self::BreakLoop(_, pos)
|
||||
| Self::Block(_, pos)
|
||||
| Self::Block(_, (pos, _))
|
||||
| Self::Assignment(_, pos)
|
||||
| Self::FnCall(_, pos)
|
||||
| Self::If(_, _, pos)
|
||||
@ -389,7 +424,7 @@ impl Stmt {
|
||||
| Self::Var(_, _, _, pos)
|
||||
| Self::TryCatch(_, pos) => *pos,
|
||||
|
||||
Self::Expr(x) => x.position(),
|
||||
Self::Expr(x) => x.start_position(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Self::Import(_, _, pos) => *pos,
|
||||
@ -405,7 +440,7 @@ impl Stmt {
|
||||
match self {
|
||||
Self::Noop(pos)
|
||||
| Self::BreakLoop(_, pos)
|
||||
| Self::Block(_, pos)
|
||||
| Self::Block(_, (pos, _))
|
||||
| Self::Assignment(_, pos)
|
||||
| Self::FnCall(_, pos)
|
||||
| Self::If(_, _, pos)
|
||||
|
@ -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-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
|
||||
----------
|
||||
|
||||
```sh
|
||||
cargo run --bin sample_app_to_run
|
||||
```
|
||||
|
||||
or with required features
|
||||
|
||||
```sh
|
||||
cargo run --bin sample_app_to_run --features feature1,feature2,feature3
|
||||
cargo run --features bin-features --bin sample_app_to_run
|
||||
```
|
||||
|
||||
|
||||
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
|
||||
cargo install --path . --bins --features decimal,metadata,debugging,rustyline
|
||||
cargo install --path . --bins --features bin-features
|
||||
```
|
||||
|
||||
or specifically:
|
||||
|
||||
```sh
|
||||
cargo install --path . --bin rhai-run --features decimal,metadata,debugging,rustyline
|
||||
cargo install --path . --bin rhai-run --features bin-features
|
||||
```
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::debugger::DebuggerCommand;
|
||||
use rhai::debugger::{BreakPoint, DebuggerCommand, DebuggerEvent};
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
|
||||
|
||||
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.
|
||||
fn print_error(input: &str, mut err: EvalAltResult) {
|
||||
let lines: Vec<_> = input.trim().split('\n').collect();
|
||||
@ -71,36 +97,38 @@ fn print_error(input: &str, mut err: EvalAltResult) {
|
||||
|
||||
/// Print debug help.
|
||||
fn print_debug_help() {
|
||||
println!("help => print this help");
|
||||
println!("quit, exit, kill => quit");
|
||||
println!("scope => print the scope");
|
||||
println!("print => print all variables de-duplicated");
|
||||
println!("print <variable> => print the current value of a variable");
|
||||
println!("help, h => print this help");
|
||||
println!("quit, q, exit, kill => quit");
|
||||
println!("scope => print the scope");
|
||||
println!("print, p => print all variables de-duplicated");
|
||||
println!("print/p <variable> => print the current value of a variable");
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
println!("imports => print all imported modules");
|
||||
println!("node => print the current AST node");
|
||||
println!("backtrace => print the current call-stack");
|
||||
println!("breakpoints => print all break-points");
|
||||
println!("enable <bp#> => enable a break-point");
|
||||
println!("disable <bp#> => disable a break-point");
|
||||
println!("delete <bp#> => delete a break-point");
|
||||
println!("clear => delete all break-points");
|
||||
println!("imports => print all imported modules");
|
||||
println!("node => print the current AST node");
|
||||
println!("list, l => print the current source line");
|
||||
println!("backtrace, bt => print the current call-stack");
|
||||
println!("info break, i b => print all break-points");
|
||||
println!("enable/en <bp#> => enable a break-point");
|
||||
println!("disable/dis <bp#> => disable a break-point");
|
||||
println!("delete, d => delete all break-points");
|
||||
println!("delete/d <bp#> => delete a break-point");
|
||||
#[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"))]
|
||||
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"))]
|
||||
println!("break .<prop> => set a new break-point for a property access");
|
||||
println!("break <func> => set a new break-point for a function call");
|
||||
println!("break/b .<prop> => set a new break-point for a property access");
|
||||
println!("break/b <func> => set a new break-point for a function call");
|
||||
println!(
|
||||
"break <func> <#args> => set a new break-point for a function call with #args arguments"
|
||||
"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!("run => restart the script evaluation from beginning");
|
||||
println!("step => go to the next expression, diving into functions");
|
||||
println!("over => go to the next expression, skipping oer functions");
|
||||
println!("next => go to the next statement, skipping over functions");
|
||||
println!("continue => continue normal execution");
|
||||
println!("throw [message] => throw an exception (message optional)");
|
||||
println!("run, r => restart the script evaluation from beginning");
|
||||
println!("step, s => go to the next expression, diving into functions");
|
||||
println!("over => go to the next expression, skipping oer functions");
|
||||
println!("next, n, <Enter> => go to the next statement, skipping over functions");
|
||||
println!("finish, f => continue until the end of the current function call");
|
||||
println!("continue, c => continue normal execution");
|
||||
println!();
|
||||
}
|
||||
|
||||
@ -220,36 +248,58 @@ fn main() {
|
||||
// Hook up debugger
|
||||
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
|
||||
|| "".into(),
|
||||
// Main debugging interface
|
||||
move |context, node, source, pos| {
|
||||
{
|
||||
let current_source = &mut *context
|
||||
.global_runtime_state_mut()
|
||||
.debugger
|
||||
.state_mut()
|
||||
.write_lock::<ImmutableString>()
|
||||
.unwrap();
|
||||
|
||||
let src = source.unwrap_or("");
|
||||
|
||||
// Check source
|
||||
if src != current_source {
|
||||
println!(">>> Source => {}", source.unwrap_or("main script"));
|
||||
*current_source = src.into();
|
||||
move |context, event, node, source, pos| {
|
||||
match event {
|
||||
DebuggerEvent::Step => (),
|
||||
DebuggerEvent::BreakPoint(n) => {
|
||||
match context.global_runtime_state().debugger.break_points()[n] {
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
BreakPoint::AtPosition { .. } => (),
|
||||
BreakPoint::AtFunctionName { ref name, .. }
|
||||
| BreakPoint::AtFunctionCall { ref name, .. } => {
|
||||
println!("! Call to function {}.", name)
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
BreakPoint::AtProperty { ref name, .. } => {
|
||||
println!("! Property {} accessed.", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
DebuggerEvent::FunctionExitWithValue(r) => {
|
||||
println!(
|
||||
"! Return from function call '{}' => {}",
|
||||
context
|
||||
.global_runtime_state()
|
||||
.debugger
|
||||
.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
|
||||
let mut input = String::new();
|
||||
|
||||
@ -267,8 +317,8 @@ fn main() {
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice()
|
||||
{
|
||||
["help", ..] => print_debug_help(),
|
||||
["exit", ..] | ["quit", ..] | ["kill", ..] => {
|
||||
["help" | "h", ..] => print_debug_help(),
|
||||
["exit" | "quit" | "q" | "kill", ..] => {
|
||||
println!("Script terminated. Bye!");
|
||||
exit(0);
|
||||
}
|
||||
@ -276,12 +326,14 @@ fn main() {
|
||||
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
|
||||
println!();
|
||||
}
|
||||
["continue", ..] => break Ok(DebuggerCommand::Continue),
|
||||
[] | ["step", ..] => break Ok(DebuggerCommand::StepInto),
|
||||
["list" | "l", ..] => print_current_source(context, source, pos, &lines),
|
||||
["continue" | "c", ..] => break Ok(DebuggerCommand::Continue),
|
||||
["finish" | "f", ..] => break Ok(DebuggerCommand::FunctionExit),
|
||||
[] | ["step" | "s", ..] => break Ok(DebuggerCommand::StepInto),
|
||||
["over", ..] => break Ok(DebuggerCommand::StepOver),
|
||||
["next", ..] => break Ok(DebuggerCommand::Next),
|
||||
["next" | "n", ..] => break Ok(DebuggerCommand::Next),
|
||||
["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 value.is::<()>() {
|
||||
println!("=> ()");
|
||||
@ -292,7 +344,7 @@ fn main() {
|
||||
eprintln!("Variable not found: {}", var_name);
|
||||
}
|
||||
}
|
||||
["print", ..] => print_scope(context.scope(), true),
|
||||
["print" | "p"] => print_scope(context.scope(), true),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
["imports", ..] => {
|
||||
for (i, (name, module)) in context
|
||||
@ -311,7 +363,7 @@ fn main() {
|
||||
println!();
|
||||
}
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
["backtrace", ..] => {
|
||||
["backtrace" | "bt", ..] => {
|
||||
for frame in context
|
||||
.global_runtime_state()
|
||||
.debugger
|
||||
@ -322,15 +374,7 @@ fn main() {
|
||||
println!("{}", frame)
|
||||
}
|
||||
}
|
||||
["clear", ..] => {
|
||||
context
|
||||
.global_runtime_state_mut()
|
||||
.debugger
|
||||
.break_points_mut()
|
||||
.clear();
|
||||
println!("All break-points cleared.");
|
||||
}
|
||||
["breakpoints", ..] => Iterator::for_each(
|
||||
["info", "break", ..] | ["i", "b", ..] => Iterator::for_each(
|
||||
context
|
||||
.global_runtime_state()
|
||||
.debugger
|
||||
@ -347,7 +391,7 @@ fn main() {
|
||||
_ => println!("[{}] {}", i + 1, bp),
|
||||
},
|
||||
),
|
||||
["enable", n, ..] => {
|
||||
["enable" | "en", n, ..] => {
|
||||
if let Ok(n) = n.parse::<usize>() {
|
||||
let range = 1..=context
|
||||
.global_runtime_state_mut()
|
||||
@ -370,7 +414,7 @@ fn main() {
|
||||
eprintln!("Invalid break-point: '{}'", n);
|
||||
}
|
||||
}
|
||||
["disable", n, ..] => {
|
||||
["disable" | "dis", n, ..] => {
|
||||
if let Ok(n) = n.parse::<usize>() {
|
||||
let range = 1..=context
|
||||
.global_runtime_state_mut()
|
||||
@ -393,7 +437,7 @@ fn main() {
|
||||
eprintln!("Invalid break-point: '{}'", n);
|
||||
}
|
||||
}
|
||||
["delete", n, ..] => {
|
||||
["delete" | "d", n, ..] => {
|
||||
if let Ok(n) = n.parse::<usize>() {
|
||||
let range = 1..=context
|
||||
.global_runtime_state_mut()
|
||||
@ -414,7 +458,15 @@ fn main() {
|
||||
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>() {
|
||||
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
||||
name: fn_name.trim().into(),
|
||||
@ -433,7 +485,7 @@ fn main() {
|
||||
}
|
||||
// Property name
|
||||
#[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 {
|
||||
name: param[1..].into(),
|
||||
enabled: true,
|
||||
@ -447,7 +499,7 @@ fn main() {
|
||||
}
|
||||
// Numeric parameter
|
||||
#[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 range = if source.is_none() {
|
||||
1..=lines.len()
|
||||
@ -472,7 +524,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
// Function name parameter
|
||||
["break", param] => {
|
||||
["break" | "b", param] => {
|
||||
let bp = rhai::debugger::BreakPoint::AtFunctionName {
|
||||
name: param.trim().into(),
|
||||
enabled: true,
|
||||
@ -485,7 +537,7 @@ fn main() {
|
||||
.push(bp);
|
||||
}
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
["break", ..] => {
|
||||
["break" | "b"] => {
|
||||
let bp = rhai::debugger::BreakPoint::AtPosition {
|
||||
source: source.unwrap_or("").into(),
|
||||
pos,
|
||||
@ -505,7 +557,7 @@ fn main() {
|
||||
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
||||
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
||||
}
|
||||
["run", ..] => {
|
||||
["run" | "r", ..] => {
|
||||
println!("Restarting script...");
|
||||
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
||||
}
|
||||
|
@ -44,9 +44,13 @@ fn print_error(input: &str, mut err: EvalAltResult) {
|
||||
/// Print help text.
|
||||
fn print_help() {
|
||||
println!("help => print this help");
|
||||
println!("keys => print list of key bindings");
|
||||
println!("quit, exit => quit");
|
||||
println!("keys => print list of key bindings");
|
||||
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!("strict => toggle on/off Strict Variables Mode");
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
@ -295,6 +299,7 @@ fn main() {
|
||||
engine.set_module_resolver(resolver);
|
||||
}
|
||||
|
||||
// Register sample functions
|
||||
engine
|
||||
.register_fn("test", |x: INT, y: INT| format!("{} {}", x, y))
|
||||
.register_fn("test", |x: &mut INT, y: INT, z: &str| {
|
||||
@ -310,6 +315,10 @@ fn main() {
|
||||
|
||||
// REPL loop
|
||||
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 ast_u = AST::empty();
|
||||
let mut ast = AST::empty();
|
||||
@ -317,35 +326,53 @@ fn main() {
|
||||
print_help();
|
||||
|
||||
'main_loop: loop {
|
||||
input.clear();
|
||||
|
||||
loop {
|
||||
let prompt = if input.is_empty() {
|
||||
"rhai-repl> "
|
||||
if let Some(replace) = replacement.take() {
|
||||
input = replace;
|
||||
if rl.add_history_entry(input.clone()) {
|
||||
history_offset += 1;
|
||||
}
|
||||
if input.contains('\n') {
|
||||
println!("[{}] ~~~~", replacement_index);
|
||||
println!("{}", input);
|
||||
println!("~~~~");
|
||||
} else {
|
||||
" > "
|
||||
};
|
||||
println!("[{}] {}", replacement_index, input);
|
||||
}
|
||||
replacement_index = 0;
|
||||
} else {
|
||||
input.clear();
|
||||
|
||||
match rl.readline(prompt) {
|
||||
// Line continuation
|
||||
Ok(mut line) if line.ends_with("\\") => {
|
||||
line.pop();
|
||||
input += line.trim_end();
|
||||
input.push('\n');
|
||||
}
|
||||
Ok(line) => {
|
||||
input += line.trim_end();
|
||||
if !input.is_empty() {
|
||||
rl.add_history_entry(input.clone());
|
||||
loop {
|
||||
let prompt = if input.is_empty() {
|
||||
"rhai-repl> "
|
||||
} else {
|
||||
" > "
|
||||
};
|
||||
|
||||
match rl.readline(prompt) {
|
||||
// Line continuation
|
||||
Ok(mut line) if line.ends_with("\\") => {
|
||||
line.pop();
|
||||
input += line.trim_end();
|
||||
input.push('\n');
|
||||
}
|
||||
Ok(line) => {
|
||||
input += line.trim_end();
|
||||
if !input.is_empty() && !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) => {
|
||||
eprintln!("Error: {:?}", err);
|
||||
break 'main_loop;
|
||||
Err(err) => {
|
||||
eprintln!("Error: {:?}", err);
|
||||
break 'main_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -370,10 +397,10 @@ fn main() {
|
||||
"history" => {
|
||||
for (i, h) in rl.history().iter().enumerate() {
|
||||
match &h.split('\n').collect::<Vec<_>>()[..] {
|
||||
[line] => println!("[{}] {}", i + 1, line),
|
||||
[line] => println!("[{}] {}", history_offset + i, line),
|
||||
lines => {
|
||||
for (x, line) in lines.iter().enumerate() {
|
||||
let number = format!("[{}]", i + 1);
|
||||
let number = format!("[{}]", history_offset + i);
|
||||
if x == 0 {
|
||||
println!("{} {}", number, line.trim_end());
|
||||
} else {
|
||||
@ -446,6 +473,57 @@ fn main() {
|
||||
);
|
||||
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;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
//! Main module defining the script evaluation [`Engine`].
|
||||
|
||||
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::tokenizer::Token;
|
||||
use crate::types::dynamic::{map_std_type_name, Union};
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{
|
||||
Dynamic, Identifier, ImmutableString, Module, Position, RhaiError, RhaiResult, Shared,
|
||||
StaticVec, ERR,
|
||||
Dynamic, Identifier, ImmutableString, Module, Position, RhaiResult, Shared, StaticVec,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
any::type_name,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt,
|
||||
num::NonZeroU8,
|
||||
@ -115,6 +115,8 @@ pub struct Engine {
|
||||
pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
|
||||
/// Custom syntax.
|
||||
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.
|
||||
pub(crate) resolve_var: Option<Box<OnVarCallback>>,
|
||||
/// Callback closure to remap tokens during parsing.
|
||||
@ -162,6 +164,7 @@ impl fmt::Debug for Engine {
|
||||
.field("disabled_symbols", &self.disabled_symbols)
|
||||
.field("custom_keywords", &self.custom_keywords)
|
||||
.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("token_mapper", &self.token_mapper.is_some())
|
||||
.field("print", &self.print.is_some())
|
||||
@ -274,6 +277,7 @@ impl Engine {
|
||||
custom_keywords: BTreeMap::new(),
|
||||
custom_syntax: BTreeMap::new(),
|
||||
|
||||
def_var_filter: None,
|
||||
resolve_var: None,
|
||||
token_mapper: None,
|
||||
|
||||
@ -337,59 +341,4 @@ impl Engine {
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ impl Engine {
|
||||
match chain_type {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ChainType::Indexing => {
|
||||
let pos = rhs.position();
|
||||
let pos = rhs.start_position();
|
||||
let root_pos = idx_val.position();
|
||||
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)?;
|
||||
|
||||
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 (try_setter, result) = {
|
||||
@ -189,8 +189,8 @@ impl Engine {
|
||||
let fn_name = crate::engine::FN_IDX_SET;
|
||||
|
||||
if let Err(err) = self.exec_fn_call(
|
||||
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
|
||||
root_pos, None, level,
|
||||
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
||||
true, root_pos, level,
|
||||
) {
|
||||
// Just ignore if there is no index setter
|
||||
if !matches!(*err, ERR::ErrorFunctionNotFound(_, _)) {
|
||||
@ -216,6 +216,7 @@ impl Engine {
|
||||
Ok(ref mut obj_ptr) => {
|
||||
self.eval_op_assignment(
|
||||
global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
||||
level,
|
||||
)
|
||||
.map_err(|err| err.fill_position(new_pos))?;
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
@ -239,8 +240,8 @@ impl Engine {
|
||||
let fn_name = crate::engine::FN_IDX_SET;
|
||||
|
||||
self.exec_fn_call(
|
||||
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
|
||||
root_pos, None, level,
|
||||
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
||||
true, root_pos, level,
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -302,6 +303,7 @@ impl Engine {
|
||||
)?;
|
||||
self.eval_op_assignment(
|
||||
global, state, lib, op_info, op_pos, val_target, root, new_val,
|
||||
level,
|
||||
)
|
||||
.map_err(|err| err.fill_position(new_pos))?;
|
||||
}
|
||||
@ -333,8 +335,8 @@ impl Engine {
|
||||
let args = &mut [target.as_mut()];
|
||||
let (mut orig_val, _) = self
|
||||
.exec_fn_call(
|
||||
global, state, lib, getter, hash, args, is_ref_mut, true, *pos,
|
||||
None, level,
|
||||
None, global, state, lib, getter, hash, args, is_ref_mut, true,
|
||||
*pos, level,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
@ -364,6 +366,7 @@ impl Engine {
|
||||
&mut (&mut orig_val).into(),
|
||||
root,
|
||||
new_val,
|
||||
level,
|
||||
)
|
||||
.map_err(|err| err.fill_position(new_pos))?;
|
||||
|
||||
@ -373,7 +376,7 @@ impl Engine {
|
||||
let hash = crate::ast::FnCallHashes::from_native(*hash_set);
|
||||
let args = &mut [target.as_mut(), &mut new_val];
|
||||
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,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
@ -386,8 +389,8 @@ impl Engine {
|
||||
let pos = Position::NONE;
|
||||
|
||||
self.exec_fn_call(
|
||||
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
|
||||
pos, None, level,
|
||||
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
||||
true, pos, level,
|
||||
)
|
||||
.map_err(
|
||||
|idx_err| match *idx_err {
|
||||
@ -408,7 +411,7 @@ impl Engine {
|
||||
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
|
||||
let args = &mut [target.as_mut()];
|
||||
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,
|
||||
)
|
||||
.map_or_else(
|
||||
@ -508,8 +511,8 @@ impl Engine {
|
||||
// Assume getters are always pure
|
||||
let (mut val, _) = self
|
||||
.exec_fn_call(
|
||||
global, state, lib, getter, hash_get, args, is_ref_mut,
|
||||
true, pos, None, level,
|
||||
None, global, state, lib, getter, hash_get, args,
|
||||
is_ref_mut, true, pos, level,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
@ -556,8 +559,8 @@ impl Engine {
|
||||
let mut arg_values = [target.as_mut(), val];
|
||||
let args = &mut arg_values;
|
||||
self.exec_fn_call(
|
||||
global, state, lib, setter, hash_set, args, is_ref_mut,
|
||||
true, pos, None, level,
|
||||
None, global, state, lib, setter, hash_set, args,
|
||||
is_ref_mut, true, pos, level,
|
||||
)
|
||||
.or_else(
|
||||
|err| match *err {
|
||||
@ -571,8 +574,8 @@ impl Engine {
|
||||
global.hash_idx_set(),
|
||||
);
|
||||
self.exec_fn_call(
|
||||
global, state, lib, fn_name, hash_set, args,
|
||||
is_ref_mut, true, pos, None, level,
|
||||
None, global, state, lib, fn_name, hash_set,
|
||||
args, is_ref_mut, true, pos, level,
|
||||
)
|
||||
.or_else(|idx_err| match *idx_err {
|
||||
ERR::ErrorIndexingType(_, _) => {
|
||||
@ -626,7 +629,7 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
// 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)?;
|
||||
|
||||
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 root = (x.2.as_str(), *var_pos);
|
||||
@ -688,7 +691,7 @@ impl Engine {
|
||||
expr => {
|
||||
let value = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?;
|
||||
let obj_ptr = &mut value.into();
|
||||
let root = ("", expr.position());
|
||||
let root = ("", expr.start_position());
|
||||
self.eval_dot_index_chain_helper(
|
||||
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
|
||||
chain_type, level, new_val,
|
||||
@ -732,7 +735,7 @@ impl Engine {
|
||||
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|
||||
|(mut values, mut pos), expr| -> RhaiResultOf<_> {
|
||||
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() {
|
||||
pos = arg_pos;
|
||||
@ -778,7 +781,7 @@ impl Engine {
|
||||
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|
||||
|(mut values, mut pos), expr| -> RhaiResultOf<_> {
|
||||
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() {
|
||||
pos = arg_pos
|
||||
@ -801,7 +804,10 @@ impl Engine {
|
||||
_ if _parent_chain_type == ChainType::Indexing => self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, lhs, level)
|
||||
.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),
|
||||
};
|
||||
@ -825,7 +831,7 @@ impl Engine {
|
||||
_ if _parent_chain_type == ChainType::Indexing => idx_values.push(
|
||||
self.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.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),
|
||||
@ -1044,7 +1050,7 @@ impl Engine {
|
||||
let pos = Position::NONE;
|
||||
|
||||
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())
|
||||
}
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
use super::{EvalContext, EvalState, GlobalRuntimeState};
|
||||
use crate::ast::{ASTNode, Expr, Stmt};
|
||||
use crate::{Dynamic, Engine, Identifier, Module, Position, RhaiResultOf, Scope};
|
||||
use std::fmt;
|
||||
use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{fmt, mem};
|
||||
|
||||
/// Callback function to initialize the debugger.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
@ -17,11 +17,22 @@ pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
|
||||
|
||||
/// Callback function for debugging.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type OnDebuggerCallback =
|
||||
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>;
|
||||
pub type OnDebuggerCallback = dyn Fn(
|
||||
&mut EvalContext,
|
||||
DebuggerEvent,
|
||||
ASTNode,
|
||||
Option<&str>,
|
||||
Position,
|
||||
) -> RhaiResultOf<DebuggerCommand>;
|
||||
/// Callback function for debugging.
|
||||
#[cfg(feature = "sync")]
|
||||
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
|
||||
pub type OnDebuggerCallback = dyn Fn(
|
||||
&mut EvalContext,
|
||||
DebuggerEvent,
|
||||
ASTNode,
|
||||
Option<&str>,
|
||||
Position,
|
||||
) -> RhaiResultOf<DebuggerCommand>
|
||||
+ Send
|
||||
+ Sync;
|
||||
|
||||
@ -36,6 +47,50 @@ pub enum DebuggerCommand {
|
||||
StepOver,
|
||||
// Run to the next statement, skipping over functions.
|
||||
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.
|
||||
@ -158,7 +213,6 @@ impl BreakPoint {
|
||||
}
|
||||
|
||||
/// A function call.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct CallStackFrame {
|
||||
/// Function name.
|
||||
@ -171,7 +225,6 @@ pub struct CallStackFrame {
|
||||
pub pos: Position,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl fmt::Display for CallStackFrame {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut fp = f.debug_tuple(&self.fn_name);
|
||||
@ -198,13 +251,12 @@ impl fmt::Display for CallStackFrame {
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct Debugger {
|
||||
/// The current status command.
|
||||
status: DebuggerCommand,
|
||||
pub(crate) status: DebuggerStatus,
|
||||
/// The current state.
|
||||
state: Dynamic,
|
||||
/// The current set of break-points.
|
||||
break_points: Vec<BreakPoint>,
|
||||
/// The current function call stack.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
call_stack: Vec<CallStackFrame>,
|
||||
}
|
||||
|
||||
@ -215,9 +267,9 @@ impl Debugger {
|
||||
pub fn new(engine: &Engine) -> Self {
|
||||
Self {
|
||||
status: if engine.debugger.is_some() {
|
||||
DebuggerCommand::StepInto
|
||||
DebuggerStatus::STEP
|
||||
} else {
|
||||
DebuggerCommand::Continue
|
||||
DebuggerStatus::CONTINUE
|
||||
},
|
||||
state: if let Some((ref init, _)) = engine.debugger {
|
||||
init()
|
||||
@ -225,7 +277,6 @@ impl Debugger {
|
||||
Dynamic::UNIT
|
||||
},
|
||||
break_points: Vec::new(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
call_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
@ -242,26 +293,17 @@ impl Debugger {
|
||||
&mut self.state
|
||||
}
|
||||
/// Get the current call stack.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn call_stack(&self) -> &[CallStackFrame] {
|
||||
&self.call_stack
|
||||
}
|
||||
/// Rewind the function call stack to a particular depth.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn rewind_call_stack(&mut self, len: usize) {
|
||||
self.call_stack.truncate(len);
|
||||
}
|
||||
/// Add a new frame to the function call stack.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_call_stack_frame(
|
||||
&mut self,
|
||||
@ -277,34 +319,37 @@ impl Debugger {
|
||||
pos,
|
||||
});
|
||||
}
|
||||
/// Get the current status of this [`Debugger`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn status(&self) -> DebuggerCommand {
|
||||
self.status
|
||||
}
|
||||
/// Get a mutable reference to the current status of this [`Debugger`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn status_mut(&mut self) -> &mut DebuggerCommand {
|
||||
&mut self.status
|
||||
}
|
||||
/// Set the status of this [`Debugger`].
|
||||
#[inline(always)]
|
||||
pub fn reset_status(&mut self, status: Option<DebuggerCommand>) {
|
||||
if let Some(cmd) = status {
|
||||
self.status = cmd;
|
||||
/// Change the current status to [`CONTINUE`][DebuggerStatus::CONTINUE] and return the previous status.
|
||||
pub(crate) fn clear_status_if(
|
||||
&mut self,
|
||||
filter: impl Fn(&DebuggerStatus) -> bool,
|
||||
) -> Option<DebuggerStatus> {
|
||||
if filter(&self.status) {
|
||||
Some(mem::replace(&mut self.status, DebuggerStatus::CONTINUE))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// 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]
|
||||
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;
|
||||
|
||||
self.break_points()
|
||||
.iter()
|
||||
.filter(|&bp| bp.is_enabled())
|
||||
.any(|bp| match bp {
|
||||
.enumerate()
|
||||
.filter(|&(_, bp)| bp.is_enabled())
|
||||
.find(|&(_, bp)| match bp {
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
@ -316,13 +361,15 @@ impl Debugger {
|
||||
node.position() == *pos && _src == source
|
||||
}
|
||||
BreakPoint::AtFunctionName { name, .. } => match node {
|
||||
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
|
||||
x.name == *name
|
||||
}
|
||||
ASTNode::Expr(Expr::FnCall(x, _))
|
||||
| ASTNode::Stmt(Stmt::FnCall(x, _))
|
||||
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, _))) => x.name == *name,
|
||||
_ => false,
|
||||
},
|
||||
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
|
||||
}
|
||||
_ => false,
|
||||
@ -333,6 +380,7 @@ impl Debugger {
|
||||
_ => false,
|
||||
},
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
}
|
||||
/// Get a slice of all [`BreakPoint`]'s.
|
||||
#[inline(always)]
|
||||
@ -349,7 +397,7 @@ impl Debugger {
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Run the debugger callback.
|
||||
/// Run the debugger callback if there is a debugging interface registered.
|
||||
#[inline(always)]
|
||||
pub(crate) fn run_debugger<'a>(
|
||||
&self,
|
||||
@ -361,26 +409,23 @@ impl Engine {
|
||||
node: impl Into<ASTNode<'a>>,
|
||||
level: usize,
|
||||
) -> RhaiResultOf<()> {
|
||||
if let Some(cmd) =
|
||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)?
|
||||
{
|
||||
global.debugger.status = cmd;
|
||||
if self.debugger.is_some() {
|
||||
if let Some(cmd) =
|
||||
self.run_debugger_with_reset_raw(scope, global, state, lib, this_ptr, node, level)?
|
||||
{
|
||||
global.debugger.status = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// When the debugger callback return [`DebuggerCommand::StepOver`], the debugger if temporarily
|
||||
/// disabled and `true` is returned.
|
||||
///
|
||||
/// It is up to the [`Engine`] to reactivate the debugger.
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn run_debugger_with_reset<'a>(
|
||||
&self,
|
||||
@ -391,61 +436,125 @@ impl Engine {
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
node: impl Into<ASTNode<'a>>,
|
||||
level: usize,
|
||||
) -> RhaiResultOf<Option<DebuggerCommand>> {
|
||||
if let Some((_, ref on_debugger)) = self.debugger {
|
||||
let node = node.into();
|
||||
) -> RhaiResultOf<Option<DebuggerStatus>> {
|
||||
if self.debugger.is_some() {
|
||||
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
|
||||
match node {
|
||||
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
|
||||
_ => (),
|
||||
}
|
||||
// Skip transitive nodes
|
||||
match node {
|
||||
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let stop = match global.debugger.status {
|
||||
DebuggerCommand::Continue => false,
|
||||
DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)),
|
||||
DebuggerCommand::StepInto | DebuggerCommand::StepOver => true,
|
||||
};
|
||||
let stop = match global.debugger.status {
|
||||
DebuggerStatus::Next(false, false) => false,
|
||||
DebuggerStatus::Next(true, false) => matches!(node, ASTNode::Stmt(_)),
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
let source = global.source.clone();
|
||||
let source = if source.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(source.as_str())
|
||||
};
|
||||
self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
|
||||
}
|
||||
/// Run the debugger callback unconditionally.
|
||||
///
|
||||
/// 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 {
|
||||
engine: self,
|
||||
scope,
|
||||
global,
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
level,
|
||||
};
|
||||
let mut context = crate::EvalContext {
|
||||
engine: self,
|
||||
scope,
|
||||
global,
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
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 {
|
||||
DebuggerCommand::Continue => {
|
||||
global.debugger.status = DebuggerCommand::Continue;
|
||||
global.debugger.status = DebuggerStatus::CONTINUE;
|
||||
Ok(None)
|
||||
}
|
||||
DebuggerCommand::Next => {
|
||||
global.debugger.status = DebuggerCommand::Continue;
|
||||
Ok(Some(DebuggerCommand::Next))
|
||||
}
|
||||
DebuggerCommand::StepInto => {
|
||||
global.debugger.status = DebuggerCommand::StepInto;
|
||||
Ok(None)
|
||||
global.debugger.status = DebuggerStatus::CONTINUE;
|
||||
Ok(Some(DebuggerStatus::NEXT))
|
||||
}
|
||||
DebuggerCommand::StepOver => {
|
||||
global.debugger.status = DebuggerCommand::Continue;
|
||||
Ok(Some(DebuggerCommand::StepOver))
|
||||
global.debugger.status = DebuggerStatus::CONTINUE;
|
||||
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 {
|
||||
|
@ -50,17 +50,22 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||
expr: &Expr,
|
||||
level: usize,
|
||||
) -> RhaiResultOf<(Target<'s>, Position)> {
|
||||
match expr {
|
||||
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() {
|
||||
// Normal variable access
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(_, None, _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr),
|
||||
(_, None, _) => {
|
||||
self.search_scope_only(scope, global, state, lib, this_ptr, expr, level)
|
||||
}
|
||||
#[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
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -136,6 +141,7 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||
expr: &Expr,
|
||||
level: usize,
|
||||
) -> RhaiResultOf<(Target<'s>, Position)> {
|
||||
// Make sure that the pointer indirection is taken only when absolutely necessary.
|
||||
|
||||
@ -148,7 +154,7 @@ impl Engine {
|
||||
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(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos),
|
||||
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
|
||||
@ -163,7 +169,7 @@ impl Engine {
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
level: 0,
|
||||
level,
|
||||
};
|
||||
match resolve_var(
|
||||
expr.get_variable_name(true).expect("`Expr::Variable`"),
|
||||
@ -236,8 +242,8 @@ impl Engine {
|
||||
);
|
||||
|
||||
self.make_function_call(
|
||||
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes, pos,
|
||||
*capture, level,
|
||||
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes,
|
||||
*capture, pos, level,
|
||||
)
|
||||
}
|
||||
|
||||
@ -297,7 +303,7 @@ impl Engine {
|
||||
.cloned()
|
||||
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
|
||||
} 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())
|
||||
};
|
||||
}
|
||||
@ -345,12 +351,13 @@ impl Engine {
|
||||
&mut (&mut concat).into(),
|
||||
("", Position::NONE),
|
||||
item,
|
||||
level,
|
||||
) {
|
||||
result = Err(err.fill_position(expr.position()));
|
||||
result = Err(err.fill_position(expr.start_position()));
|
||||
break;
|
||||
}
|
||||
|
||||
pos = expr.position();
|
||||
pos = expr.start_position();
|
||||
}
|
||||
|
||||
result.map(|_| concat)
|
||||
@ -503,7 +510,7 @@ impl Engine {
|
||||
|
||||
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),
|
||||
|
@ -5,6 +5,12 @@ use crate::{Engine, Identifier};
|
||||
use std::prelude::v1::*;
|
||||
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.
|
||||
/// 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.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) constants: Option<
|
||||
crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>,
|
||||
>,
|
||||
pub(crate) constants: Option<GlobalConstants>,
|
||||
/// Debugging interface.
|
||||
#[cfg(feature = "debugging")]
|
||||
pub debugger: super::Debugger,
|
||||
|
@ -11,11 +11,14 @@ mod target;
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
pub use chaining::{ChainArgument, ChainType};
|
||||
#[cfg(feature = "debugging")]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use debugger::CallStackFrame;
|
||||
#[cfg(feature = "debugging")]
|
||||
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback, OnDebuggingInit};
|
||||
pub use debugger::{
|
||||
BreakPoint, CallStackFrame, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus,
|
||||
OnDebuggerCallback, OnDebuggingInit,
|
||||
};
|
||||
pub use eval_context::EvalContext;
|
||||
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 target::{calc_index, calc_offset_len, Target};
|
||||
|
136
src/eval/stmt.rs
136
src/eval/stmt.rs
@ -1,6 +1,6 @@
|
||||
//! Module defining functions for evaluating a statement.
|
||||
|
||||
use super::{EvalState, GlobalRuntimeState, Target};
|
||||
use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
|
||||
use crate::ast::{
|
||||
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||
};
|
||||
@ -120,6 +120,7 @@ impl Engine {
|
||||
target: &mut Target,
|
||||
root: (&str, Position),
|
||||
new_val: Dynamic,
|
||||
level: usize,
|
||||
) -> RhaiResultOf<()> {
|
||||
if target.is_read_only() {
|
||||
// Assignment to constant variable
|
||||
@ -152,9 +153,10 @@ impl Engine {
|
||||
|
||||
let hash = hash_op_assign;
|
||||
let args = &mut [lhs_ptr_inner, &mut new_val];
|
||||
let level = level + 1;
|
||||
|
||||
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(_) => {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
@ -164,7 +166,7 @@ impl Engine {
|
||||
{
|
||||
// Expand to `var = var op rhs`
|
||||
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"))]
|
||||
@ -238,7 +240,7 @@ impl Engine {
|
||||
|
||||
if let Ok(rhs_val) = rhs_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 {
|
||||
let (mut lhs_ptr, pos) = search_val;
|
||||
@ -263,8 +265,9 @@ impl Engine {
|
||||
&mut lhs_ptr,
|
||||
(var_name, pos),
|
||||
rhs_val,
|
||||
level,
|
||||
)
|
||||
.map_err(|err| err.fill_position(rhs.position()))
|
||||
.map_err(|err| err.fill_position(rhs.start_position()))
|
||||
.map(|_| Dynamic::UNIT)
|
||||
} else {
|
||||
search_result.map(|_| Dynamic::UNIT)
|
||||
@ -280,7 +283,7 @@ impl Engine {
|
||||
.map(Dynamic::flatten);
|
||||
|
||||
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`
|
||||
match lhs {
|
||||
@ -683,7 +686,7 @@ impl Engine {
|
||||
|
||||
loop_result
|
||||
} else {
|
||||
Err(ERR::ErrorFor(expr.position()).into())
|
||||
Err(ERR::ErrorFor(expr.start_position()).into())
|
||||
}
|
||||
} else {
|
||||
iter_result
|
||||
@ -799,9 +802,14 @@ impl Engine {
|
||||
// Empty return
|
||||
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
|
||||
Stmt::Var(expr, x, options, _) => {
|
||||
Stmt::Var(expr, x, options, pos) => {
|
||||
let var_name = &x.name;
|
||||
|
||||
let entry_type = if options.contains(AST_OPTION_CONSTANT) {
|
||||
AccessMode::ReadOnly
|
||||
} else {
|
||||
@ -809,48 +817,79 @@ impl Engine {
|
||||
};
|
||||
let export = options.contains(AST_OPTION_EXPORTED);
|
||||
|
||||
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
|
||||
let result = if let Some(ref filter) = self.def_var_filter {
|
||||
let shadowing = scope.contains(var_name);
|
||||
let scope_level = state.scope_level;
|
||||
let is_const = entry_type == AccessMode::ReadOnly;
|
||||
let context = EvalContext {
|
||||
engine: self,
|
||||
scope,
|
||||
global,
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
level: level,
|
||||
};
|
||||
|
||||
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());
|
||||
match filter(var_name, is_const, scope_level, shadowing, &context) {
|
||||
Ok(true) => None,
|
||||
Ok(false) => Some(Err(ERR::ErrorRuntime(
|
||||
format!("Variable cannot be defined: {}", var_name).into(),
|
||||
*pos,
|
||||
)
|
||||
.into())),
|
||||
err @ Err(_) => Some(err),
|
||||
}
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
} 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
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &expr, level)
|
||||
.and_then(|v| {
|
||||
let typ = v.type_name();
|
||||
v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
|
||||
self.make_type_mismatch_err::<crate::ImmutableString>(
|
||||
"",
|
||||
typ,
|
||||
expr.position(),
|
||||
)
|
||||
})
|
||||
@ -877,7 +917,7 @@ impl Engine {
|
||||
if let Ok(path) = path_result {
|
||||
use crate::ModuleResolver;
|
||||
|
||||
let path_pos = expr.position();
|
||||
let path_pos = expr.start_position();
|
||||
|
||||
let resolver = global.embedded_module_resolver.clone();
|
||||
|
||||
|
219
src/func/call.rs
219
src/func/call.rs
@ -328,6 +328,8 @@ impl Engine {
|
||||
result.as_ref().map(Box::as_ref)
|
||||
}
|
||||
|
||||
/// # Main Entry-Point
|
||||
///
|
||||
/// Call a native Rust function registered with the [`Engine`].
|
||||
///
|
||||
/// # WARNING
|
||||
@ -347,6 +349,7 @@ impl Engine {
|
||||
is_ref_mut: bool,
|
||||
is_op_assign: bool,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut global.num_operations, pos)?;
|
||||
@ -365,43 +368,92 @@ impl Engine {
|
||||
is_op_assign,
|
||||
);
|
||||
|
||||
if let Some(FnResolutionCacheEntry { func, source }) = func {
|
||||
assert!(func.is_native());
|
||||
if func.is_some() {
|
||||
let is_method = func.map(|f| f.func.is_method()).unwrap_or(false);
|
||||
|
||||
// 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);
|
||||
}
|
||||
// Push a new call stack frame
|
||||
#[cfg(feature = "debugging")]
|
||||
let orig_call_stack_len = global.debugger.call_stack().len();
|
||||
|
||||
// Run external function
|
||||
let source = match (source.as_str(), parent_source.as_str()) {
|
||||
("", "") => None,
|
||||
("", s) | (s, _) => Some(s),
|
||||
};
|
||||
let mut _result = if let Some(FnResolutionCacheEntry { func, source }) = func {
|
||||
assert!(func.is_native());
|
||||
|
||||
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() {
|
||||
func.get_plugin_fn()
|
||||
.expect("plugin function")
|
||||
.call(context, args)
|
||||
let source = match (source.as_str(), parent_source.as_str()) {
|
||||
("", "") => None,
|
||||
("", s) | (s, _) => Some(s),
|
||||
};
|
||||
|
||||
#[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 {
|
||||
func.get_native_fn().expect("native function")(context, args)
|
||||
unreachable!("`Some`");
|
||||
};
|
||||
|
||||
// Restore the original reference
|
||||
if let Some(bk) = backup {
|
||||
bk.restore_first_arg(args)
|
||||
#[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 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)
|
||||
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.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
@ -443,7 +495,7 @@ impl Engine {
|
||||
(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.
|
||||
///
|
||||
/// # 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 `()`!
|
||||
pub(crate) fn exec_fn_call(
|
||||
&self,
|
||||
scope: Option<&mut Scope>,
|
||||
global: &mut GlobalRuntimeState,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
@ -549,7 +604,6 @@ impl Engine {
|
||||
is_ref_mut: bool,
|
||||
is_method_call: bool,
|
||||
pos: Position,
|
||||
scope: Option<&mut Scope>,
|
||||
level: usize,
|
||||
) -> 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)?;
|
||||
|
||||
let _scope = scope;
|
||||
let _level = level;
|
||||
let _is_method_call = is_method_call;
|
||||
|
||||
// These may be redirected from method style calls.
|
||||
@ -612,6 +665,8 @@ impl Engine {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let level = level + 1;
|
||||
|
||||
// Script-defined function call?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if let Some(FnResolutionCacheEntry { func, mut source }) = self
|
||||
@ -651,8 +706,6 @@ impl Engine {
|
||||
// Method call of script function - map first argument to `this`
|
||||
let (first_arg, rest_args) = args.split_first_mut().unwrap();
|
||||
|
||||
let level = _level + 1;
|
||||
|
||||
let result = self.call_script_fn(
|
||||
scope,
|
||||
global,
|
||||
@ -661,8 +714,8 @@ impl Engine {
|
||||
&mut Some(*first_arg),
|
||||
func,
|
||||
rest_args,
|
||||
pos,
|
||||
true,
|
||||
pos,
|
||||
level,
|
||||
);
|
||||
|
||||
@ -679,10 +732,8 @@ impl Engine {
|
||||
.change_first_arg_to_copy(args);
|
||||
}
|
||||
|
||||
let level = _level + 1;
|
||||
|
||||
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
|
||||
@ -702,7 +753,7 @@ impl Engine {
|
||||
// Native function call
|
||||
let hash = hashes.native;
|
||||
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
|
||||
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,
|
||||
)
|
||||
}
|
||||
@ -804,7 +855,7 @@ impl Engine {
|
||||
|
||||
// Map it to name(args) in function-call style
|
||||
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,
|
||||
)
|
||||
}
|
||||
@ -876,7 +927,7 @@ impl Engine {
|
||||
args.extend(call_args.iter_mut());
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
@ -901,9 +952,9 @@ impl Engine {
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
level: usize,
|
||||
arg_expr: &Expr,
|
||||
constants: &[Dynamic],
|
||||
level: usize,
|
||||
) -> RhaiResultOf<(Dynamic, Position)> {
|
||||
Ok((
|
||||
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)?;
|
||||
value
|
||||
} 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],
|
||||
constants: &[Dynamic],
|
||||
hashes: FnCallHashes,
|
||||
pos: Position,
|
||||
capture_scope: bool,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
let mut first_arg = first_arg;
|
||||
@ -951,7 +1014,7 @@ impl Engine {
|
||||
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
||||
let arg = first_arg.unwrap();
|
||||
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>() {
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
@ -986,7 +1049,7 @@ impl Engine {
|
||||
KEYWORD_FN_PTR if total_args == 1 => {
|
||||
let arg = first_arg.unwrap();
|
||||
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
|
||||
return arg_value
|
||||
@ -1001,7 +1064,7 @@ impl Engine {
|
||||
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
|
||||
let first = first_arg.unwrap();
|
||||
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>() {
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
@ -1018,7 +1081,7 @@ impl Engine {
|
||||
.iter()
|
||||
.try_fold(fn_curry, |mut curried, expr| -> RhaiResultOf<_> {
|
||||
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);
|
||||
Ok(curried)
|
||||
@ -1032,7 +1095,7 @@ impl Engine {
|
||||
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
|
||||
let arg = first_arg.unwrap();
|
||||
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());
|
||||
}
|
||||
|
||||
@ -1041,14 +1104,14 @@ impl Engine {
|
||||
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
|
||||
let first = first_arg.unwrap();
|
||||
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
|
||||
.into_immutable_string()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
|
||||
|
||||
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
|
||||
@ -1068,7 +1131,7 @@ impl Engine {
|
||||
KEYWORD_IS_DEF_VAR if total_args == 1 => {
|
||||
let arg = first_arg.unwrap();
|
||||
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
|
||||
.into_immutable_string()
|
||||
.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 arg = first_arg.unwrap();
|
||||
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
|
||||
.into_immutable_string()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
|
||||
@ -1130,7 +1193,7 @@ impl Engine {
|
||||
.map(|&v| v)
|
||||
.chain(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()))
|
||||
})?;
|
||||
args.extend(curry.iter_mut());
|
||||
@ -1141,7 +1204,7 @@ impl Engine {
|
||||
|
||||
return self
|
||||
.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,
|
||||
)
|
||||
.map(|(v, _)| v);
|
||||
@ -1162,12 +1225,12 @@ impl Engine {
|
||||
|
||||
// func(x, ...) -> x.func(...)
|
||||
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()))
|
||||
})?;
|
||||
|
||||
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() {
|
||||
target = target.into_owned();
|
||||
@ -1198,7 +1261,7 @@ impl Engine {
|
||||
.chain(a_expr.iter())
|
||||
.try_for_each(|expr| {
|
||||
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()))
|
||||
})?;
|
||||
@ -1208,7 +1271,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@ -1248,13 +1311,14 @@ impl Engine {
|
||||
arg_values.push(Dynamic::UNIT);
|
||||
|
||||
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()))
|
||||
})?;
|
||||
|
||||
// Get target reference to first argument
|
||||
let first_arg = &args_expr[0];
|
||||
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"))]
|
||||
self.inc_operations(&mut global.num_operations, _pos)?;
|
||||
@ -1278,7 +1342,7 @@ impl Engine {
|
||||
} else {
|
||||
// func(..., ...) or func(mod::x, ...)
|
||||
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()))
|
||||
})?;
|
||||
args.extend(arg_values.iter_mut());
|
||||
@ -1312,34 +1376,27 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let level = level + 1;
|
||||
|
||||
match func {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Some(f) if f.is_script() => {
|
||||
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() {
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
let new_scope = &mut Scope::new();
|
||||
let result = self.call_script_fn(
|
||||
new_scope, global, state, lib, &mut None, fn_def, &mut args, true, pos, level,
|
||||
);
|
||||
|
||||
let mut source = module.id_raw().clone();
|
||||
mem::swap(&mut global.source, &mut source);
|
||||
global.source = source;
|
||||
|
||||
let level = level + 1;
|
||||
|
||||
let result = self.call_script_fn(
|
||||
new_scope, global, state, lib, &mut None, fn_def, &mut args, pos, true,
|
||||
level,
|
||||
);
|
||||
|
||||
global.source = source;
|
||||
|
||||
result
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
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
|
||||
.get_plugin_fn()
|
||||
.expect("plugin function")
|
||||
@ -1350,7 +1407,7 @@ impl Engine {
|
||||
|
||||
Some(f) if f.is_native() => {
|
||||
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);
|
||||
self.check_return_value(result, pos)
|
||||
}
|
||||
|
@ -70,6 +70,8 @@ pub struct NativeCallContext<'a> {
|
||||
lib: &'a [&'a Module],
|
||||
/// [Position] of the function call.
|
||||
pos: Position,
|
||||
/// The current nesting level of function calls.
|
||||
level: usize,
|
||||
}
|
||||
|
||||
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 M,
|
||||
Position,
|
||||
usize,
|
||||
)> for NativeCallContext<'a>
|
||||
{
|
||||
#[inline(always)]
|
||||
@ -91,6 +94,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
||||
&'a GlobalRuntimeState,
|
||||
&'a M,
|
||||
Position,
|
||||
usize,
|
||||
),
|
||||
) -> Self {
|
||||
Self {
|
||||
@ -100,6 +104,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
||||
global: Some(value.3),
|
||||
lib: value.4.as_ref(),
|
||||
pos: value.5,
|
||||
level: value.6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,6 +121,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
||||
global: None,
|
||||
lib: value.2.as_ref(),
|
||||
pos: Position::NONE,
|
||||
level: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,6 +147,7 @@ impl<'a> NativeCallContext<'a> {
|
||||
global: None,
|
||||
lib,
|
||||
pos: Position::NONE,
|
||||
level: 0,
|
||||
}
|
||||
}
|
||||
/// _(internals)_ Create a new [`NativeCallContext`].
|
||||
@ -158,6 +165,7 @@ impl<'a> NativeCallContext<'a> {
|
||||
global: &'a GlobalRuntimeState,
|
||||
lib: &'a [&Module],
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
engine,
|
||||
@ -166,6 +174,7 @@ impl<'a> NativeCallContext<'a> {
|
||||
global: Some(global),
|
||||
lib,
|
||||
pos,
|
||||
level,
|
||||
}
|
||||
}
|
||||
/// The current [`Engine`].
|
||||
@ -186,6 +195,12 @@ impl<'a> NativeCallContext<'a> {
|
||||
pub const fn position(&self) -> Position {
|
||||
self.pos
|
||||
}
|
||||
/// Current nesting level of function calls.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn call_level(&self) -> usize {
|
||||
self.level
|
||||
}
|
||||
/// The current source.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
@ -306,6 +321,7 @@ impl<'a> NativeCallContext<'a> {
|
||||
|
||||
self.engine()
|
||||
.exec_fn_call(
|
||||
None,
|
||||
&mut global,
|
||||
&mut state,
|
||||
self.lib,
|
||||
@ -315,8 +331,7 @@ impl<'a> NativeCallContext<'a> {
|
||||
is_ref_mut,
|
||||
is_method_call,
|
||||
Position::NONE,
|
||||
None,
|
||||
0,
|
||||
self.level + 1,
|
||||
)
|
||||
.map(|(r, _)| r)
|
||||
}
|
||||
@ -429,3 +444,11 @@ pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Optio
|
||||
#[cfg(feature = "sync")]
|
||||
pub type OnVarCallback =
|
||||
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;
|
||||
|
@ -4,12 +4,14 @@
|
||||
use super::call::FnCallArgs;
|
||||
use crate::ast::ScriptFnDef;
|
||||
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;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
impl Engine {
|
||||
/// # Main Entry-Point
|
||||
///
|
||||
/// Call a script-defined function.
|
||||
///
|
||||
/// 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>,
|
||||
fn_def: &ScriptFnDef,
|
||||
args: &mut FnCallArgs,
|
||||
pos: Position,
|
||||
rewind_scope: bool,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
#[inline(never)]
|
||||
@ -41,13 +43,19 @@ impl Engine {
|
||||
err: RhaiError,
|
||||
pos: Position,
|
||||
) -> 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(
|
||||
name,
|
||||
fn_def
|
||||
.lib
|
||||
.as_ref()
|
||||
.and_then(|m| m.id().map(str::to_string))
|
||||
.unwrap_or_else(|| global.source.to_string()),
|
||||
source.unwrap_or_else(|| global.source.to_string()),
|
||||
err,
|
||||
pos,
|
||||
)
|
||||
@ -59,22 +67,26 @@ impl Engine {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut global.num_operations, pos)?;
|
||||
|
||||
if fn_def.body.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
// Check for stack overflow
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if level > self.max_call_levels() {
|
||||
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();
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let orig_imports_len = global.num_imports();
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let orig_call_stack_len = global.debugger.call_stack().len();
|
||||
|
||||
// Put arguments into scope as variables
|
||||
@ -85,44 +97,58 @@ impl Engine {
|
||||
|
||||
// Push a new call stack frame
|
||||
#[cfg(feature = "debugging")]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
global.debugger.push_call_stack_frame(
|
||||
fn_def.name.clone(),
|
||||
scope
|
||||
.iter()
|
||||
.skip(orig_scope_len)
|
||||
.map(|(_, _, v)| v.clone())
|
||||
.collect(),
|
||||
global.source.clone(),
|
||||
pos,
|
||||
);
|
||||
if self.debugger.is_some() {
|
||||
global.debugger.push_call_stack_frame(
|
||||
fn_def.name.clone(),
|
||||
scope
|
||||
.iter()
|
||||
.skip(orig_scope_len)
|
||||
.map(|(_, _, v)| v.clone())
|
||||
.collect(),
|
||||
global.source.clone(),
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
// Merge in encapsulated environment, if any
|
||||
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
|
||||
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
||||
|
||||
let lib = if let Some(ref fn_lib) = fn_def.lib {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
lib
|
||||
};
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let mut lib_merged = crate::StaticVec::with_capacity(lib.len() + 1);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if let Some(ref modules) = fn_def.global {
|
||||
for (n, m) in modules.iter().cloned() {
|
||||
let (lib, constants) = if let Some(crate::ast::EncapsulatedEnviron {
|
||||
lib: ref fn_lib,
|
||||
ref imports,
|
||||
ref constants,
|
||||
}) = fn_def.environ
|
||||
{
|
||||
for (n, m) in imports.iter().cloned() {
|
||||
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
|
||||
let result = self
|
||||
let mut _result = self
|
||||
.eval_stmt_block(
|
||||
scope,
|
||||
global,
|
||||
@ -155,6 +181,31 @@ impl Engine {
|
||||
_ => 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
|
||||
if rewind_scope {
|
||||
scope.rewind(orig_scope_len);
|
||||
@ -165,15 +216,16 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
global.truncate_imports(orig_imports_len);
|
||||
|
||||
// Restore constants
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if let Some(constants) = constants {
|
||||
global.constants = constants;
|
||||
}
|
||||
|
||||
// Restore state
|
||||
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
|
||||
|
||||
// Pop the call stack
|
||||
#[cfg(feature = "debugging")]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
global.debugger.rewind_call_stack(orig_call_stack_len);
|
||||
|
||||
result
|
||||
_result
|
||||
}
|
||||
|
||||
// Does a script-defined function exist?
|
||||
|
11
src/lib.rs
11
src/lib.rs
@ -154,6 +154,8 @@ pub use eval::EvalContext;
|
||||
pub use func::{NativeCallContext, RegisterNativeFunction};
|
||||
pub use module::{FnNamespace, Module};
|
||||
pub use tokenizer::Position;
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub use types::Instant;
|
||||
pub use types::{
|
||||
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
|
||||
};
|
||||
@ -164,7 +166,7 @@ pub use types::{
|
||||
pub mod debugger {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
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
|
||||
@ -256,9 +258,14 @@ pub use parser::ParseState;
|
||||
pub use ast::{
|
||||
ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
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(not(feature = "no_float"))]
|
||||
pub use ast::FloatWrapper;
|
||||
|
@ -1696,33 +1696,21 @@ impl Module {
|
||||
let mut module = Module::new();
|
||||
|
||||
// Extra modules left become sub-modules
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let mut func_global = None;
|
||||
let mut imports = StaticVec::new_const();
|
||||
|
||||
if result.is_ok() {
|
||||
global
|
||||
.scan_imports_raw()
|
||||
.skip(orig_imports_len)
|
||||
.for_each(|(k, m)| {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if func_global.is_none() {
|
||||
func_global = Some(StaticVec::new());
|
||||
}
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
func_global
|
||||
.as_mut()
|
||||
.expect("`Some`")
|
||||
.push((k.clone(), m.clone()));
|
||||
|
||||
imports.push((k.clone(), m.clone()));
|
||||
module.set_sub_module(k.clone(), m.clone());
|
||||
});
|
||||
}
|
||||
|
||||
// Restore global state
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
global.constants = orig_constants;
|
||||
}
|
||||
let constants = std::mem::replace(&mut global.constants, orig_constants);
|
||||
global.truncate_imports(orig_imports_len);
|
||||
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
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if ast.has_functions() {
|
||||
{
|
||||
ast.shared_lib()
|
||||
.iter_fn()
|
||||
.filter(|&f| match f.metadata.access {
|
||||
@ -1761,15 +1746,20 @@ impl Module {
|
||||
})
|
||||
.filter(|&f| f.func.is_script())
|
||||
.for_each(|f| {
|
||||
// Encapsulate AST environment
|
||||
let mut func = f
|
||||
.func
|
||||
.get_script_fn_def()
|
||||
.expect("script-defined function")
|
||||
.as_ref()
|
||||
.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);
|
||||
});
|
||||
}
|
||||
|
@ -147,6 +147,7 @@ impl<'a> OptimizerState<'a> {
|
||||
false,
|
||||
false,
|
||||
Position::NONE,
|
||||
0,
|
||||
)
|
||||
.ok()
|
||||
.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() => {
|
||||
state.set_dirty();
|
||||
|
||||
let pos = condition.position();
|
||||
let pos = condition.start_position();
|
||||
let mut expr = mem::take(condition);
|
||||
optimize_expr(&mut expr, state, false);
|
||||
|
||||
*stmt = if preserve_result {
|
||||
// -> { 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 {
|
||||
// -> 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)
|
||||
{
|
||||
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
|
||||
@ -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)
|
||||
{
|
||||
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 }
|
||||
@ -530,11 +534,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
mem::take(&mut block.statements),
|
||||
Stmt::Block(
|
||||
def_stmt.into_boxed_slice(),
|
||||
x.def_case.position().or_else(*pos),
|
||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||
)
|
||||
.into(),
|
||||
)),
|
||||
match_expr.position(),
|
||||
match_expr.start_position(),
|
||||
);
|
||||
} else {
|
||||
// Promote the matched case
|
||||
@ -545,7 +549,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
true,
|
||||
false,
|
||||
);
|
||||
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.position());
|
||||
*stmt =
|
||||
Stmt::Block(statements.into_boxed_slice(), block.statements.positions());
|
||||
}
|
||||
|
||||
state.set_dirty();
|
||||
@ -585,11 +590,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
mem::take(&mut block.statements),
|
||||
Stmt::Block(
|
||||
def_stmt.into_boxed_slice(),
|
||||
x.def_case.position().or_else(*pos),
|
||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||
)
|
||||
.into(),
|
||||
)),
|
||||
match_expr.position(),
|
||||
match_expr.start_position(),
|
||||
);
|
||||
} else {
|
||||
// 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);
|
||||
*stmt = Stmt::Block(
|
||||
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);
|
||||
*stmt = Stmt::Block(
|
||||
def_stmt.into_boxed_slice(),
|
||||
x.def_case.position().or_else(*pos),
|
||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||
);
|
||||
}
|
||||
// switch
|
||||
@ -703,7 +708,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
if preserve_result {
|
||||
statements.push(Stmt::Noop(pos))
|
||||
}
|
||||
*stmt = Stmt::Block(statements.into_boxed_slice(), pos);
|
||||
*stmt =
|
||||
Stmt::Block(statements.into_boxed_slice(), (pos, Position::NONE));
|
||||
} else {
|
||||
*stmt = Stmt::Noop(pos);
|
||||
};
|
||||
@ -720,7 +726,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
|
||||
.into_boxed_slice(),
|
||||
body.position(),
|
||||
body.positions(),
|
||||
);
|
||||
}
|
||||
// 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() {
|
||||
[] => {
|
||||
state.set_dirty();
|
||||
*stmt = Stmt::Noop(*pos);
|
||||
*stmt = Stmt::Noop(pos.0);
|
||||
}
|
||||
// Only one statement which is not block-dependent - promote
|
||||
[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(
|
||||
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
|
||||
.into_boxed_slice(),
|
||||
x.try_block.position(),
|
||||
x.try_block.positions(),
|
||||
);
|
||||
}
|
||||
// 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() {
|
||||
state.set_dirty();
|
||||
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() {
|
||||
state.set_dirty();
|
||||
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,
|
||||
body: crate::ast::StmtBlock::NONE,
|
||||
params: fn_def.params.clone(),
|
||||
lib: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
global: None,
|
||||
environ: None,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: None,
|
||||
|
@ -27,15 +27,23 @@ def_package! {
|
||||
|
||||
#[export_module]
|
||||
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_index"))]
|
||||
pub fn stack_trace(ctx: NativeCallContext) -> Array {
|
||||
pub fn back_trace(ctx: NativeCallContext) -> Array {
|
||||
if let Some(global) = ctx.global_runtime_state() {
|
||||
global
|
||||
.debugger
|
||||
.call_stack()
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|crate::debugger::CallStackFrame { fn_name, args, .. }| {
|
||||
fn_name != "back_trace" || !args.is_empty()
|
||||
})
|
||||
.map(
|
||||
|frame @ crate::debugger::CallStackFrame {
|
||||
fn_name: _fn_name,
|
||||
|
154
src/parser.rs
154
src/parser.rs
@ -52,11 +52,11 @@ pub struct ParseState<'e> {
|
||||
pub entry_stack_len: usize,
|
||||
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||
#[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
|
||||
/// 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.
|
||||
/// 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"))]
|
||||
pub allow_capture: bool,
|
||||
/// Encapsulates a local stack with imported [module][crate::Module] names.
|
||||
@ -85,7 +85,7 @@ impl<'e> ParseState<'e> {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
external_vars: BTreeMap::new(),
|
||||
external_vars: Vec::new(),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
allow_capture: true,
|
||||
interned_strings: StringsInterner::new(),
|
||||
@ -128,8 +128,11 @@ impl<'e> ParseState<'e> {
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if self.allow_capture {
|
||||
if index.is_none() && !self.external_vars.contains_key(name) {
|
||||
self.external_vars.insert(name.into(), _pos);
|
||||
if index.is_none() && !self.external_vars.iter().any(|v| v.name == name) {
|
||||
self.external_vars.push(crate::ast::Ident {
|
||||
name: name.into(),
|
||||
pos: _pos,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.allow_capture = true
|
||||
@ -303,7 +306,7 @@ impl Expr {
|
||||
|
||||
Err(
|
||||
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.
|
||||
@ -323,7 +326,7 @@ impl Expr {
|
||||
|
||||
Err(
|
||||
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,
|
||||
hashes,
|
||||
args,
|
||||
pos: settings.pos,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(settings.pos));
|
||||
@ -582,6 +586,7 @@ fn parse_fn_call(
|
||||
namespace,
|
||||
hashes,
|
||||
args,
|
||||
pos: settings.pos,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(settings.pos));
|
||||
@ -649,7 +654,7 @@ fn parse_index_chain(
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(lhs.position()))
|
||||
.into_err(lhs.start_position()))
|
||||
}
|
||||
|
||||
Expr::CharConstant(_, _)
|
||||
@ -660,7 +665,7 @@ fn parse_index_chain(
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"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(
|
||||
"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"))]
|
||||
@ -682,7 +687,7 @@ fn parse_index_chain(
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(lhs.position()))
|
||||
.into_err(lhs.start_position()))
|
||||
}
|
||||
|
||||
Expr::CharConstant(_, _)
|
||||
@ -693,7 +698,7 @@ fn parse_index_chain(
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"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(
|
||||
"Array access expects integer index, not a float".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
.into_err(x.start_position()))
|
||||
}
|
||||
// lhs[char]
|
||||
x @ Expr::CharConstant(_, _) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a character".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
.into_err(x.start_position()))
|
||||
}
|
||||
// lhs[()]
|
||||
x @ Expr::Unit(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not ()".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
.into_err(x.start_position()))
|
||||
}
|
||||
// lhs[??? && ???], lhs[??? || ???]
|
||||
x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a boolean".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
.into_err(x.start_position()))
|
||||
}
|
||||
// lhs[true], lhs[false]
|
||||
x @ Expr::BoolConstant(_, _) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a boolean".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
.into_err(x.start_position()))
|
||||
}
|
||||
// All other expressions
|
||||
_ => (),
|
||||
@ -1045,7 +1050,7 @@ fn parse_switch(
|
||||
|
||||
let (hash, range) = if let Some(expr) = expr {
|
||||
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>();
|
||||
@ -1055,14 +1060,14 @@ fn parse_switch(
|
||||
} else if let Some(range) = value.read_lock::<InclusiveRange>() {
|
||||
(None, Some((*range.start(), *range.end(), true)))
|
||||
} 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 {
|
||||
let hasher = &mut get_hasher();
|
||||
value.hash(hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
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)
|
||||
}
|
||||
@ -1258,12 +1263,14 @@ fn parse_primary(
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
new_state.external_vars.iter().try_for_each(
|
||||
|(captured_var, &pos)| -> ParseResult<_> {
|
||||
let index = state.access_var(captured_var, pos);
|
||||
|crate::ast::Ident { name, pos }| -> ParseResult<_> {
|
||||
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
|
||||
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 {
|
||||
Ok(())
|
||||
}
|
||||
@ -1677,6 +1684,7 @@ fn parse_unary(
|
||||
name: state.get_identifier("", "-"),
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
|
||||
args,
|
||||
pos,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -1703,6 +1711,7 @@ fn parse_unary(
|
||||
name: state.get_identifier("", "+"),
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
|
||||
args,
|
||||
pos,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -1720,6 +1729,7 @@ fn parse_unary(
|
||||
name: state.get_identifier("", "!"),
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
|
||||
args,
|
||||
pos,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -1748,7 +1758,7 @@ fn make_assignment_stmt(
|
||||
}
|
||||
Expr::Property(_, _) => None,
|
||||
// Anything other than a property after dotting (e.g. a method call) is not an l-value
|
||||
ref e => Some(e.position()),
|
||||
ref e => Some(e.start_position()),
|
||||
},
|
||||
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
|
||||
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(_, _) => unreachable!("unexpected Expr::Property in indexing"),
|
||||
e if parent_is_dot => Some(e.position()),
|
||||
e if parent_is_dot => Some(e.start_position()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -1767,7 +1777,7 @@ fn make_assignment_stmt(
|
||||
match lhs {
|
||||
// const_expr = rhs
|
||||
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
|
||||
Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment(
|
||||
@ -1809,10 +1819,8 @@ fn make_assignment_stmt(
|
||||
op_pos,
|
||||
)),
|
||||
// expr[???] = rhs, expr.??? = rhs
|
||||
ref expr => {
|
||||
Err(PERR::AssignmentToInvalidLHS("".to_string())
|
||||
.into_err(expr.position()))
|
||||
}
|
||||
ref expr => Err(PERR::AssignmentToInvalidLHS("".to_string())
|
||||
.into_err(expr.start_position())),
|
||||
}
|
||||
}
|
||||
Some(err_pos) => {
|
||||
@ -1827,7 +1835,7 @@ fn make_assignment_stmt(
|
||||
)
|
||||
.into_err(op_pos)),
|
||||
// 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))
|
||||
}
|
||||
// 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 {
|
||||
name: state.get_identifier("", op),
|
||||
hashes: FnCallHashes::from_native(hash),
|
||||
pos,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -2077,7 +2086,10 @@ fn parse_binary_op(
|
||||
| Token::LessThan
|
||||
| Token::LessThanEqualsTo
|
||||
| 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 => {
|
||||
let rhs = args.pop().unwrap();
|
||||
@ -2106,6 +2118,7 @@ fn parse_binary_op(
|
||||
Token::In => {
|
||||
// Swap the arguments
|
||||
let current_lhs = args.remove(0);
|
||||
let pos = current_lhs.start_position();
|
||||
args.push(current_lhs);
|
||||
args.shrink_to_fit();
|
||||
|
||||
@ -2127,6 +2140,7 @@ fn parse_binary_op(
|
||||
.map_or(false, Option::is_some) =>
|
||||
{
|
||||
let hash = calc_fn_hash(&s, 2);
|
||||
let pos = args[0].start_position();
|
||||
|
||||
FnCallExpr {
|
||||
hashes: if is_valid_function_name(&s) {
|
||||
@ -2140,7 +2154,10 @@ fn parse_binary_op(
|
||||
.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, 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 var_def = Ident {
|
||||
name: name.clone(),
|
||||
@ -2729,13 +2752,10 @@ fn parse_block(
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let orig_imports_len = state.imports.len();
|
||||
|
||||
loop {
|
||||
let end_pos = loop {
|
||||
// Terminated?
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
(Token::RightBrace, _) => {
|
||||
eat_token(input, Token::RightBrace);
|
||||
break;
|
||||
}
|
||||
(Token::RightBrace, _) => break eat_token(input, Token::RightBrace),
|
||||
(Token::EOF, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::RightBrace.into(),
|
||||
@ -2762,10 +2782,7 @@ fn parse_block(
|
||||
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
// { ... stmt }
|
||||
(Token::RightBrace, _) => {
|
||||
eat_token(input, Token::RightBrace);
|
||||
break;
|
||||
}
|
||||
(Token::RightBrace, _) => break eat_token(input, Token::RightBrace),
|
||||
// { ... stmt;
|
||||
(Token::SemiColon, _) if need_semicolon => {
|
||||
eat_token(input, Token::SemiColon);
|
||||
@ -2788,7 +2805,7 @@ fn parse_block(
|
||||
.into_err(*pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
state.stack.truncate(state.entry_stack_len);
|
||||
state.entry_stack_len = prev_entry_stack_len;
|
||||
@ -2796,7 +2813,10 @@ fn parse_block(
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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.
|
||||
@ -2959,25 +2979,25 @@ fn parse_stmt(
|
||||
|
||||
Token::If => parse_if(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())
|
||||
}
|
||||
Token::Do if settings.default_options.allow_loop => {
|
||||
Token::Do if settings.default_options.allow_looping => {
|
||||
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())
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
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))
|
||||
}
|
||||
|
||||
@ -3185,9 +3205,8 @@ fn parse_fn(
|
||||
access,
|
||||
params,
|
||||
body,
|
||||
lib: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
global: None,
|
||||
environ: None,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: if comments.is_empty() {
|
||||
@ -3240,6 +3259,7 @@ fn make_curry_from_externals(
|
||||
num_externals + 1,
|
||||
)),
|
||||
args,
|
||||
pos,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(pos);
|
||||
@ -3249,7 +3269,7 @@ fn make_curry_from_externals(
|
||||
let mut statements = StaticVec::with_capacity(externals.len() + 1);
|
||||
statements.extend(externals.into_iter().map(Stmt::Share));
|
||||
statements.push(Stmt::Expr(expr));
|
||||
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos).into())
|
||||
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
|
||||
}
|
||||
|
||||
/// Parse an anonymous function definition.
|
||||
@ -3312,20 +3332,21 @@ fn parse_anon_fn(
|
||||
// External variables may need to be processed in a consistent order,
|
||||
// so extract them into a list.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
let externals: StaticVec<Identifier> = state
|
||||
.external_vars
|
||||
.iter()
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect();
|
||||
let (mut params, externals) = {
|
||||
let externals: StaticVec<Identifier> = state
|
||||
.external_vars
|
||||
.iter()
|
||||
.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")]
|
||||
let mut params = StaticVec::with_capacity(params_list.len());
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
params.extend(externals.iter().cloned());
|
||||
|
||||
params.append(&mut params_list);
|
||||
|
||||
// Create unique function name by hashing the script body plus the parameters.
|
||||
@ -3341,9 +3362,8 @@ fn parse_anon_fn(
|
||||
access: crate::FnAccess::Public,
|
||||
params,
|
||||
body: body.into(),
|
||||
lib: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
global: None,
|
||||
environ: None,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: None,
|
||||
|
@ -37,9 +37,9 @@ fn check_struct_sizes() {
|
||||
assert_eq!(
|
||||
size_of::<NativeCallContext>(),
|
||||
if cfg!(feature = "no_position") {
|
||||
64
|
||||
} else {
|
||||
72
|
||||
} else {
|
||||
80
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ use std::{
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use std::time::Instant;
|
||||
pub use std::time::Instant;
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(target_family = "wasm")]
|
||||
use instant::Instant;
|
||||
pub use instant::Instant;
|
||||
|
||||
/// The message: 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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
|
@ -30,6 +30,8 @@ pub enum EvalAltResult {
|
||||
/// Syntax error.
|
||||
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.
|
||||
ErrorVariableNotFound(String, Position),
|
||||
/// 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::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::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
|
||||
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?,
|
||||
Self::ErrorDataRace(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"),
|
||||
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::ErrorFor(_) => f.write_str("For loop expects a type that is iterable")?,
|
||||
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::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()) {
|
||||
("", s) => write!(f, "Output type is incorrect, expecting {}", s),
|
||||
(r, "") => write!(f, "Output type is incorrect: {}", r),
|
||||
@ -274,6 +277,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorBitFieldBounds(_, _, _)
|
||||
| Self::ErrorIndexingType(_, _)
|
||||
| Self::ErrorFor(_)
|
||||
| Self::ErrorVariableExists(_, _)
|
||||
| Self::ErrorVariableNotFound(_, _)
|
||||
| Self::ErrorModuleNotFound(_, _)
|
||||
| Self::ErrorDataRace(_, _)
|
||||
@ -364,7 +368,8 @@ impl EvalAltResult {
|
||||
Self::ErrorIndexingType(t, _) => {
|
||||
map.insert("type".into(), t.into());
|
||||
}
|
||||
Self::ErrorVariableNotFound(v, _)
|
||||
Self::ErrorVariableExists(v, _)
|
||||
| Self::ErrorVariableNotFound(v, _)
|
||||
| Self::ErrorDataRace(v, _)
|
||||
| Self::ErrorAssignmentToConstant(v, _) => {
|
||||
map.insert("variable".into(), v.into());
|
||||
@ -415,6 +420,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorBitFieldBounds(_, _, pos)
|
||||
| Self::ErrorIndexingType(_, pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableExists(_, pos)
|
||||
| Self::ErrorVariableNotFound(_, pos)
|
||||
| Self::ErrorModuleNotFound(_, pos)
|
||||
| Self::ErrorDataRace(_, pos)
|
||||
@ -463,6 +469,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorBitFieldBounds(_, _, pos)
|
||||
| Self::ErrorIndexingType(_, pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableExists(_, pos)
|
||||
| Self::ErrorVariableNotFound(_, pos)
|
||||
| Self::ErrorModuleNotFound(_, pos)
|
||||
| Self::ErrorDataRace(_, pos)
|
||||
|
@ -9,6 +9,8 @@ pub mod parse_error;
|
||||
pub mod scope;
|
||||
|
||||
pub use dynamic::Dynamic;
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub use dynamic::Instant;
|
||||
pub use error::EvalAltResult;
|
||||
pub use fn_ptr::FnPtr;
|
||||
pub use immutable_string::ImmutableString;
|
||||
|
@ -167,6 +167,10 @@ pub enum ParseErrorType {
|
||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||
/// Wrapped value is the error message (if any).
|
||||
AssignmentToInvalidLHS(String),
|
||||
/// A variable is already defined.
|
||||
///
|
||||
/// Only appears when variables shadowing is disabled.
|
||||
VariableExists(String),
|
||||
/// A variable is not found.
|
||||
///
|
||||
/// 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::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::ModuleUndefined(s) => write!(f, "Undefined module: {}", s),
|
||||
|
||||
|
@ -9,7 +9,12 @@ use rhai::Map;
|
||||
|
||||
#[test]
|
||||
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_index"))]
|
||||
@ -18,7 +23,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
||||
"
|
||||
fn foo(x) {
|
||||
if x >= 5 {
|
||||
stack_trace()
|
||||
back_trace()
|
||||
} else {
|
||||
foo(x+1)
|
||||
}
|
||||
@ -30,7 +35,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(r.len(), 6);
|
||||
|
||||
assert_eq!(engine.eval::<INT>("len(stack_trace())")?, 0);
|
||||
assert_eq!(engine.eval::<INT>("len(back_trace())")?, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -41,7 +46,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.on_debugger(
|
||||
engine.register_debugger(
|
||||
|| {
|
||||
// Say, use an object map for the debugger state
|
||||
let mut state = Map::new();
|
||||
@ -50,7 +55,7 @@ fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
|
||||
state.insert("foo".into(), false.into());
|
||||
Dynamic::from_map(state)
|
||||
},
|
||||
|context, _, _, _| {
|
||||
|context, _, _, _, _| {
|
||||
// Get global runtime state
|
||||
let global = context.global_runtime_state_mut();
|
||||
|
||||
|
@ -514,3 +514,47 @@ fn test_module_file() -> Result<(), Box<EvalAltResult>> {
|
||||
Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
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(())
|
||||
}
|
||||
|
@ -78,33 +78,24 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
||||
|
||||
assert_eq!(
|
||||
format!("{:?}", ast),
|
||||
"AST { source: \"\", body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }"
|
||||
);
|
||||
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(123 @ 1:53)] }");
|
||||
|
||||
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
||||
|
||||
assert_eq!(
|
||||
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 }")?;
|
||||
|
||||
assert_eq!(
|
||||
format!("{:?}", ast),
|
||||
"AST { source: \"\", body: Block[], functions: Module, resolver: None }"
|
||||
);
|
||||
assert_eq!(format!("{:?}", ast), "AST { body: [] }");
|
||||
|
||||
engine.set_optimization_level(OptimizationLevel::Full);
|
||||
|
||||
let ast = engine.compile("abs(-42)")?;
|
||||
|
||||
assert_eq!(
|
||||
format!("{:?}", ast),
|
||||
"AST { source: \"\", body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }"
|
||||
);
|
||||
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||
|
||||
#[test]
|
||||
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());
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
@ -120,3 +120,26 @@ fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user