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
|
- uses: actions/checkout@v2
|
||||||
- run: rustup toolchain update nightly && rustup default nightly
|
- run: rustup toolchain update nightly && rustup default nightly
|
||||||
- name: Run benchmark
|
- name: Run benchmark
|
||||||
run: cargo +nightly bench | tee output.txt
|
run: cargo +nightly bench --features decimal,metadata,serde,debugging | tee output.txt
|
||||||
- name: Store benchmark result
|
- name: Store benchmark result
|
||||||
uses: rhysd/github-action-benchmark@v1
|
uses: rhysd/github-action-benchmark@v1
|
||||||
with:
|
with:
|
||||||
|
40
CHANGELOG.md
40
CHANGELOG.md
@ -12,25 +12,57 @@ Bug fixes
|
|||||||
* In `Scope::clone_visible`, constants are now properly cloned as constants.
|
* In `Scope::clone_visible`, constants are now properly cloned as constants.
|
||||||
* Variables introduced inside `try` blocks are now properly cleaned up upon an exception.
|
* Variables introduced inside `try` blocks are now properly cleaned up upon an exception.
|
||||||
* Off-by-one error in character positions after a comment line is now fixed.
|
* Off-by-one error in character positions after a comment line is now fixed.
|
||||||
|
* Globally-defined constants are now encapsulated correctly inside a loaded module and no longer spill across call boundaries.
|
||||||
|
* Type names display is fixed.
|
||||||
|
|
||||||
Script-breaking changes
|
Script-breaking changes
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
* For consistency, the `export` statement no longer exports multiple variables.
|
* For consistency with the `import` statement, the `export` statement no longer exports multiple variables.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* A debugging interface is added.
|
* A debugging interface is added.
|
||||||
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
||||||
* A new package, `DebuggingPackage`, is added which contains the `stack_trace` function to get the current call stack anywhere in a script.
|
* A new package, `DebuggingPackage`, is added which contains the `back_trace` function to get the current call stack anywhere in a script.
|
||||||
|
* `Engine::set_allow_shadowing` is added to allow/disallow variables _shadowing_, with new errors `EvalAltResult::ErrorVariableExists` and `ParseErrorType::VariableExists`.
|
||||||
|
* `Engine::on_def_var` allows registering a closure which can decide whether a variable definition is allow to continue, or should fail with an error.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* `rhai-repl` tool has a few more commands, such as `strict` to turn on/off _Strict Variables Mode_ and `optimize` to turn on/off script optimization.
|
|
||||||
* Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required.
|
* Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required.
|
||||||
* The `no_module` feature now eliminates large sections of code via feature gates.
|
* The `no_module` feature now eliminates large sections of code via feature gates.
|
||||||
|
* Debug display of `AST` is improved.
|
||||||
|
* `NativeCallContext::call_level()` is added to give the current nesting level of function calls.
|
||||||
|
* A new feature, `bin-features`, pulls in all the required features for `bin` tools.
|
||||||
|
* `AST` position display is improved:
|
||||||
|
* `Expr::start_position` is added to give the beginning of the expression (not the operator's position).
|
||||||
|
* `StmtBlock` and `Stmt::Block` now keep the position of the closing `}` as well.
|
||||||
|
|
||||||
|
REPL tool changes
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The REPL bin tool, `rhai-rpl`, has been enhanced.
|
||||||
|
|
||||||
|
### Build changes
|
||||||
|
|
||||||
|
* The `rustyline` feature is now required in order to build `rhai-repl`.
|
||||||
|
* Therefore, `rhai-repl` is no longer automatically built when using a simple `cargo build` with default features.
|
||||||
|
|
||||||
|
### Line editor
|
||||||
|
|
||||||
|
* `rhai-repl` now uses [`rustyline`](https://crates.io/crates/rustyline) as a line editor with history.
|
||||||
|
* Shift-Enter can now be used to enter multiple lines without having to attach the `\` continuation character the end of each line.
|
||||||
|
|
||||||
|
### New commands
|
||||||
|
|
||||||
|
* `strict` to turn on/off _Strict Variables Mode_.
|
||||||
|
* `optimize` to turn on/off script optimization.
|
||||||
|
* `history` to print lines history.
|
||||||
|
* `!!`, `!`_num_ and `!`_text_ to recall a history line.
|
||||||
|
* `keys` to print all key bindings.
|
||||||
|
|
||||||
|
|
||||||
Version 1.4.1
|
Version 1.4.1
|
||||||
@ -551,7 +583,7 @@ Breaking changes
|
|||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* Line continuation (via `\`) and multi-line literal strings (wrapped with <code>\`</code>) support are added.
|
* Line continuation (via `\`) and multi-line literal strings (wrapped with `` ` ``) support are added.
|
||||||
* Rhai scripts can now start with a shebang `#!` which is ignored.
|
* Rhai scripts can now start with a shebang `#!` which is ignored.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
|
@ -55,12 +55,16 @@ unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for ident
|
|||||||
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata
|
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata
|
||||||
debugging = ["internals"] # enable debugging
|
debugging = ["internals"] # enable debugging
|
||||||
|
|
||||||
|
# compiling for no-std
|
||||||
no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"]
|
no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"]
|
||||||
|
|
||||||
# compiling for WASM
|
# compiling for WASM
|
||||||
wasm-bindgen = ["instant/wasm-bindgen"]
|
wasm-bindgen = ["instant/wasm-bindgen"]
|
||||||
stdweb = ["instant/stdweb"]
|
stdweb = ["instant/stdweb"]
|
||||||
|
|
||||||
|
# compiling bin tools
|
||||||
|
bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rhai-repl"
|
name = "rhai-repl"
|
||||||
required-features = ["rustyline"]
|
required-features = ["rustyline"]
|
||||||
|
@ -190,8 +190,8 @@ impl Engine {
|
|||||||
&mut this_ptr,
|
&mut this_ptr,
|
||||||
fn_def,
|
fn_def,
|
||||||
&mut args,
|
&mut args,
|
||||||
Position::NONE,
|
|
||||||
rewind_scope,
|
rewind_scope,
|
||||||
|
Position::NONE,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -15,12 +15,12 @@ impl Engine {
|
|||||||
/// > `Fn(name: &str, index: usize, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>`
|
/// > `Fn(name: &str, index: usize, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>`
|
||||||
///
|
///
|
||||||
/// where:
|
/// where:
|
||||||
|
/// * `name`: name of the variable.
|
||||||
/// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the
|
/// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the
|
||||||
/// variable is supposed to reside. Offsets start from 1, with 1 meaning the last variable in
|
/// variable is supposed to reside. Offsets start from 1, with 1 meaning the last variable in
|
||||||
/// the current [`Scope`][crate::Scope]. Essentially the correct variable is at position
|
/// the current [`Scope`][crate::Scope]. Essentially the correct variable is at position
|
||||||
/// `scope.len() - index`. If `index` is zero, then there is no pre-calculated offset position
|
/// `scope.len() - index`. If `index` is zero, then there is no pre-calculated offset position
|
||||||
/// and a search through the current [`Scope`][crate::Scope] must be performed.
|
/// and a search through the current [`Scope`][crate::Scope] must be performed.
|
||||||
///
|
|
||||||
/// * `context`: the current [evaluation context][`EvalContext`].
|
/// * `context`: the current [evaluation context][`EvalContext`].
|
||||||
///
|
///
|
||||||
/// ## Return value
|
/// ## Return value
|
||||||
@ -63,6 +63,67 @@ impl Engine {
|
|||||||
self.resolve_var = Some(Box::new(callback));
|
self.resolve_var = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Provide a callback that will be invoked before the definition of each variable .
|
||||||
|
///
|
||||||
|
/// # Callback Function Signature
|
||||||
|
///
|
||||||
|
/// The callback function signature takes the following form:
|
||||||
|
///
|
||||||
|
/// > `Fn(name: &str, is_const: bool, block_level: usize, will_shadow: bool, context: &EvalContext) -> Result<bool, Box<EvalAltResult>>`
|
||||||
|
///
|
||||||
|
/// where:
|
||||||
|
/// * `name`: name of the variable to be defined.
|
||||||
|
/// * `is_const`: `true` if the statement is `const`, otherwise it is `let`.
|
||||||
|
/// * `block_level`: the current nesting level of statement blocks, with zero being the global level
|
||||||
|
/// * `will_shadow`: will the variable _shadow_ an existing variable?
|
||||||
|
/// * `context`: the current [evaluation context][`EvalContext`].
|
||||||
|
///
|
||||||
|
/// ## Return value
|
||||||
|
///
|
||||||
|
/// * `Ok(true)`: continue with normal variable definition.
|
||||||
|
/// * `Ok(false)`: deny the variable definition with an [runtime error][EvalAltResult::ErrorRuntime].
|
||||||
|
///
|
||||||
|
/// ## Raising errors
|
||||||
|
///
|
||||||
|
/// Return `Err(...)` if there is an error.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Register a variable definition filter.
|
||||||
|
/// engine.on_def_var(|name, is_const, _, _, _| {
|
||||||
|
/// // Disallow defining MYSTIC_NUMBER as a constant
|
||||||
|
/// if name == "MYSTIC_NUMBER" && is_const {
|
||||||
|
/// Ok(false)
|
||||||
|
/// } else {
|
||||||
|
/// Ok(true)
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // The following runs fine:
|
||||||
|
/// engine.eval::<i64>("let MYSTIC_NUMBER = 42;")?;
|
||||||
|
///
|
||||||
|
/// // The following will cause an error:
|
||||||
|
/// engine.eval::<i64>("const MYSTIC_NUMBER = 42;")?;
|
||||||
|
///
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn on_def_var(
|
||||||
|
&mut self,
|
||||||
|
callback: impl Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool>
|
||||||
|
+ SendSync
|
||||||
|
+ 'static,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.def_var_filter = Some(Box::new(callback));
|
||||||
|
self
|
||||||
|
}
|
||||||
/// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens.
|
/// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
@ -264,11 +325,12 @@ impl Engine {
|
|||||||
/// Exported under the `debugging` feature only.
|
/// Exported under the `debugging` feature only.
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_debugger(
|
pub fn register_debugger(
|
||||||
&mut self,
|
&mut self,
|
||||||
init: impl Fn() -> Dynamic + SendSync + 'static,
|
init: impl Fn() -> Dynamic + SendSync + 'static,
|
||||||
callback: impl Fn(
|
callback: impl Fn(
|
||||||
&mut EvalContext,
|
&mut EvalContext,
|
||||||
|
crate::eval::DebuggerEvent,
|
||||||
crate::ast::ASTNode,
|
crate::ast::ASTNode,
|
||||||
Option<&str>,
|
Option<&str>,
|
||||||
Position,
|
Position,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
//! Module defining the public API of the Rhai engine.
|
//! Module defining the public API of the Rhai engine.
|
||||||
|
|
||||||
|
pub mod type_names;
|
||||||
|
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
|
|
||||||
pub mod run;
|
pub mod run;
|
||||||
|
@ -17,9 +17,11 @@ pub struct LanguageOptions {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub allow_anonymous_fn: bool,
|
pub allow_anonymous_fn: bool,
|
||||||
/// Is looping allowed?
|
/// Is looping allowed?
|
||||||
pub allow_loop: bool,
|
pub allow_looping: bool,
|
||||||
/// Strict variables mode?
|
/// Strict variables mode?
|
||||||
pub strict_var: bool,
|
pub strict_var: bool,
|
||||||
|
/// Is variables shadowing allowed?
|
||||||
|
pub allow_shadowing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageOptions {
|
impl LanguageOptions {
|
||||||
@ -32,8 +34,9 @@ impl LanguageOptions {
|
|||||||
allow_stmt_expr: true,
|
allow_stmt_expr: true,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
allow_anonymous_fn: true,
|
allow_anonymous_fn: true,
|
||||||
allow_loop: true,
|
allow_looping: true,
|
||||||
strict_var: false,
|
strict_var: false,
|
||||||
|
allow_shadowing: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,12 +97,12 @@ impl Engine {
|
|||||||
/// Is looping allowed?
|
/// Is looping allowed?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_looping(&self) -> bool {
|
pub fn allow_looping(&self) -> bool {
|
||||||
self.options.allow_loop
|
self.options.allow_looping
|
||||||
}
|
}
|
||||||
/// Set whether looping is allowed.
|
/// Set whether looping is allowed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_allow_looping(&mut self, enable: bool) {
|
pub fn set_allow_looping(&mut self, enable: bool) {
|
||||||
self.options.allow_loop = enable;
|
self.options.allow_looping = enable;
|
||||||
}
|
}
|
||||||
/// Is strict variables mode enabled?
|
/// Is strict variables mode enabled?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -111,4 +114,14 @@ impl Engine {
|
|||||||
pub fn set_strict_variables(&mut self, enable: bool) {
|
pub fn set_strict_variables(&mut self, enable: bool) {
|
||||||
self.options.strict_var = enable;
|
self.options.strict_var = enable;
|
||||||
}
|
}
|
||||||
|
/// Is variables shadowing allowed?
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn allow_shadowing(&self) -> bool {
|
||||||
|
self.options.allow_shadowing
|
||||||
|
}
|
||||||
|
/// Set whether variables shadowing is allowed.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_allow_shadowing(&mut self, enable: bool) {
|
||||||
|
self.options.allow_shadowing = enable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
131
src/api/type_names.rs
Normal file
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).
|
//! Module defining the AST (abstract syntax tree).
|
||||||
|
|
||||||
use super::{Expr, FnAccess, Stmt, StmtBlock, AST_OPTION_FLAGS};
|
use super::{Expr, FnAccess, Stmt, StmtBlock, AST_OPTION_FLAGS::*};
|
||||||
use crate::{Dynamic, FnNamespace, Identifier, Position, StaticVec};
|
use crate::{Dynamic, FnNamespace, Identifier, Position, StaticVec};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
|
fmt,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
ops::{Add, AddAssign},
|
ops::{Add, AddAssign},
|
||||||
};
|
};
|
||||||
@ -14,7 +15,7 @@ use std::{
|
|||||||
/// # Thread Safety
|
/// # Thread Safety
|
||||||
///
|
///
|
||||||
/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AST {
|
pub struct AST {
|
||||||
/// Source of the [`AST`].
|
/// Source of the [`AST`].
|
||||||
/// No source if string is empty.
|
/// No source if string is empty.
|
||||||
@ -23,7 +24,7 @@ pub struct AST {
|
|||||||
body: StmtBlock,
|
body: StmtBlock,
|
||||||
/// Script-defined functions.
|
/// Script-defined functions.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
functions: crate::Shared<crate::Module>,
|
lib: crate::Shared<crate::Module>,
|
||||||
/// Embedded module resolver, if any.
|
/// Embedded module resolver, if any.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: Option<crate::Shared<crate::module::resolvers::StaticModuleResolver>>,
|
resolver: Option<crate::Shared<crate::module::resolvers::StaticModuleResolver>>,
|
||||||
@ -36,6 +37,31 @@ impl Default for AST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for AST {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut fp = f.debug_struct("AST");
|
||||||
|
|
||||||
|
if !self.source.is_empty() {
|
||||||
|
fp.field("source: ", &self.source);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
if let Some(ref resolver) = self.resolver {
|
||||||
|
fp.field("resolver: ", resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.field("body", &self.body.as_slice());
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
if !self.lib.is_empty() {
|
||||||
|
for (_, _, _, _, ref fn_def) in self.lib.iter_script_fn() {
|
||||||
|
let sig = fn_def.to_string();
|
||||||
|
fp.field(&sig, &fn_def.body.as_slice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fp.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AST {
|
impl AST {
|
||||||
/// Create a new [`AST`].
|
/// Create a new [`AST`].
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
@ -47,9 +73,9 @@ impl AST {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: Identifier::new_const(),
|
source: Identifier::new_const(),
|
||||||
body: StmtBlock::new(statements, Position::NONE),
|
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
functions: functions.into(),
|
lib: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
}
|
}
|
||||||
@ -65,9 +91,9 @@ impl AST {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: Identifier::new_const(),
|
source: Identifier::new_const(),
|
||||||
body: StmtBlock::new(statements, Position::NONE),
|
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
functions: functions.into(),
|
lib: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
}
|
}
|
||||||
@ -115,7 +141,7 @@ impl AST {
|
|||||||
source: Identifier::new_const(),
|
source: Identifier::new_const(),
|
||||||
body: StmtBlock::NONE,
|
body: StmtBlock::NONE,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
functions: crate::Module::new().into(),
|
lib: crate::Module::new().into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
}
|
}
|
||||||
@ -140,7 +166,7 @@ impl AST {
|
|||||||
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
|
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
|
||||||
let source = source.into();
|
let source = source.into();
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
crate::Shared::get_mut(&mut self.functions)
|
crate::Shared::get_mut(&mut self.lib)
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|m| m.set_id(source.clone()));
|
.map(|m| m.set_id(source.clone()));
|
||||||
self.source = source;
|
self.source = source;
|
||||||
@ -181,7 +207,7 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn has_functions(&self) -> bool {
|
pub fn has_functions(&self) -> bool {
|
||||||
!self.functions.is_empty()
|
!self.lib.is_empty()
|
||||||
}
|
}
|
||||||
/// Get the internal shared [`Module`][crate::Module] containing all script-defined functions.
|
/// Get the internal shared [`Module`][crate::Module] containing all script-defined functions.
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
@ -189,7 +215,7 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn shared_lib(&self) -> &crate::Shared<crate::Module> {
|
pub(crate) fn shared_lib(&self) -> &crate::Shared<crate::Module> {
|
||||||
&self.functions
|
&self.lib
|
||||||
}
|
}
|
||||||
/// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions.
|
/// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
@ -200,7 +226,7 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn shared_lib(&self) -> &crate::Shared<crate::Module> {
|
pub fn shared_lib(&self) -> &crate::Shared<crate::Module> {
|
||||||
&self.functions
|
&self.lib
|
||||||
}
|
}
|
||||||
/// Get the embedded [module resolver][`ModuleResolver`].
|
/// Get the embedded [module resolver][`ModuleResolver`].
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
@ -260,12 +286,12 @@ impl AST {
|
|||||||
&self,
|
&self,
|
||||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut functions = crate::Module::new();
|
let mut lib = crate::Module::new();
|
||||||
functions.merge_filtered(&self.functions, &filter);
|
lib.merge_filtered(&self.lib, &filter);
|
||||||
Self {
|
Self {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
body: StmtBlock::NONE,
|
body: StmtBlock::NONE,
|
||||||
functions: functions.into(),
|
lib: lib.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: self.resolver.clone(),
|
resolver: self.resolver.clone(),
|
||||||
}
|
}
|
||||||
@ -279,7 +305,7 @@ impl AST {
|
|||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
body: self.body.clone(),
|
body: self.body.clone(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
functions: crate::Module::new().into(),
|
lib: crate::Module::new().into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: self.resolver.clone(),
|
resolver: self.resolver.clone(),
|
||||||
}
|
}
|
||||||
@ -472,24 +498,24 @@ impl AST {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
let functions = {
|
let lib = {
|
||||||
let mut functions = self.functions.as_ref().clone();
|
let mut lib = self.lib.as_ref().clone();
|
||||||
functions.merge_filtered(&other.functions, &_filter);
|
lib.merge_filtered(&other.lib, &_filter);
|
||||||
functions
|
lib
|
||||||
};
|
};
|
||||||
|
|
||||||
if !other.source.is_empty() {
|
if !other.source.is_empty() {
|
||||||
Self::new_with_source(
|
Self::new_with_source(
|
||||||
merged,
|
merged,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
functions,
|
lib,
|
||||||
other.source.clone(),
|
other.source.clone(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Self::new(
|
Self::new(
|
||||||
merged,
|
merged,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
functions,
|
lib,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -562,9 +588,9 @@ impl AST {
|
|||||||
self.body.extend(other.body.into_iter());
|
self.body.extend(other.body.into_iter());
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if !other.functions.is_empty() {
|
if !other.lib.is_empty() {
|
||||||
crate::func::native::shared_make_mut(&mut self.functions)
|
crate::func::native::shared_make_mut(&mut self.lib)
|
||||||
.merge_filtered(&other.functions, &_filter);
|
.merge_filtered(&other.lib, &_filter);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -599,20 +625,32 @@ impl AST {
|
|||||||
&mut self,
|
&mut self,
|
||||||
filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool,
|
filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
if !self.functions.is_empty() {
|
if !self.lib.is_empty() {
|
||||||
crate::func::native::shared_make_mut(&mut self.functions)
|
crate::func::native::shared_make_mut(&mut self.lib).retain_script_functions(filter);
|
||||||
.retain_script_functions(filter);
|
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// _(internals)_ Iterate through all function definitions.
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
///
|
||||||
|
/// Not available under `no_function`.
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[inline]
|
||||||
|
pub fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
|
||||||
|
self.lib
|
||||||
|
.iter_script_fn()
|
||||||
|
.map(|(_, _, _, _, fn_def)| fn_def.as_ref())
|
||||||
|
}
|
||||||
/// Iterate through all function definitions.
|
/// Iterate through all function definitions.
|
||||||
///
|
///
|
||||||
/// Not available under `no_function`.
|
/// Not available under `no_function`.
|
||||||
|
#[cfg(not(feature = "internals"))]
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
|
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
|
||||||
self.functions
|
self.lib
|
||||||
.iter_script_fn()
|
.iter_script_fn()
|
||||||
.map(|(_, _, _, _, fn_def)| fn_def.as_ref())
|
.map(|(_, _, _, _, fn_def)| fn_def.as_ref())
|
||||||
}
|
}
|
||||||
@ -622,7 +660,7 @@ impl AST {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn iter_functions<'a>(&'a self) -> impl Iterator<Item = super::ScriptFnMetadata> + 'a {
|
pub fn iter_functions<'a>(&'a self) -> impl Iterator<Item = super::ScriptFnMetadata> + 'a {
|
||||||
self.functions
|
self.lib
|
||||||
.iter_script_fn()
|
.iter_script_fn()
|
||||||
.map(|(_, _, _, _, fn_def)| fn_def.as_ref().into())
|
.map(|(_, _, _, _, fn_def)| fn_def.as_ref().into())
|
||||||
}
|
}
|
||||||
@ -632,7 +670,7 @@ impl AST {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_functions(&mut self) -> &mut Self {
|
pub fn clear_functions(&mut self) -> &mut Self {
|
||||||
self.functions = crate::Module::new().into();
|
self.lib = crate::Module::new().into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Clear all statements in the [`AST`], leaving only function definitions.
|
/// Clear all statements in the [`AST`], leaving only function definitions.
|
||||||
@ -707,16 +745,11 @@ impl AST {
|
|||||||
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
|
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
|
||||||
self.statements().iter().filter_map(move |stmt| match stmt {
|
self.statements().iter().filter_map(move |stmt| match stmt {
|
||||||
Stmt::Var(expr, name, options, _)
|
Stmt::Var(expr, name, options, _)
|
||||||
if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants
|
if options.contains(AST_OPTION_CONSTANT) && include_constants
|
||||||
|| !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT)
|
|| !options.contains(AST_OPTION_CONSTANT) && include_variables =>
|
||||||
&& include_variables =>
|
|
||||||
{
|
{
|
||||||
if let Some(value) = expr.get_literal_value() {
|
if let Some(value) = expr.get_literal_value() {
|
||||||
Some((
|
Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value))
|
||||||
name.as_str(),
|
|
||||||
options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT),
|
|
||||||
value,
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -871,6 +904,6 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn lib(&self) -> &crate::Module {
|
pub fn lib(&self) -> &crate::Module {
|
||||||
&self.functions
|
&self.lib
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ impl FnCallHashes {
|
|||||||
|
|
||||||
/// _(internals)_ A function call.
|
/// _(internals)_ A function call.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug, Clone, Default, Hash)]
|
#[derive(Clone, Default, Hash)]
|
||||||
pub struct FnCallExpr {
|
pub struct FnCallExpr {
|
||||||
/// Namespace of the function, if any.
|
/// Namespace of the function, if any.
|
||||||
///
|
///
|
||||||
@ -191,6 +191,27 @@ pub struct FnCallExpr {
|
|||||||
pub constants: StaticVec<Dynamic>,
|
pub constants: StaticVec<Dynamic>,
|
||||||
/// Does this function call capture the parent scope?
|
/// Does this function call capture the parent scope?
|
||||||
pub capture_parent_scope: bool,
|
pub capture_parent_scope: bool,
|
||||||
|
/// [Position] of the function name.
|
||||||
|
pub pos: Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for FnCallExpr {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut ff = f.debug_struct("FnCallExpr");
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
self.namespace.as_ref().map(|ns| ff.field("namespace", ns));
|
||||||
|
ff.field("name", &self.name)
|
||||||
|
.field("hash", &self.hashes)
|
||||||
|
.field("arg_exprs", &self.args);
|
||||||
|
if !self.constants.is_empty() {
|
||||||
|
ff.field("constant_args", &self.constants);
|
||||||
|
}
|
||||||
|
if self.capture_parent_scope {
|
||||||
|
ff.field("capture_parent_scope", &self.capture_parent_scope);
|
||||||
|
}
|
||||||
|
ff.field("pos", &self.pos);
|
||||||
|
ff.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FnCallExpr {
|
impl FnCallExpr {
|
||||||
@ -419,7 +440,7 @@ impl Default for Expr {
|
|||||||
|
|
||||||
impl fmt::Debug for Expr {
|
impl fmt::Debug for Expr {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mut display_pos = self.position();
|
let mut display_pos = self.start_position();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::DynamicConstant(value, _) => write!(f, "{:?}", value),
|
Self::DynamicConstant(value, _) => write!(f, "{:?}", value),
|
||||||
@ -459,26 +480,12 @@ impl fmt::Debug for Expr {
|
|||||||
f.write_str(")")
|
f.write_str(")")
|
||||||
}
|
}
|
||||||
Self::Property(x, _) => write!(f, "Property({})", x.2),
|
Self::Property(x, _) => write!(f, "Property({})", x.2),
|
||||||
Self::Stack(x, _) => write!(f, "StackSlot({})", x),
|
Self::Stack(x, _) => write!(f, "ConstantArg#{}", x),
|
||||||
Self::Stmt(x) => {
|
Self::Stmt(x) => {
|
||||||
f.write_str("ExprStmtBlock")?;
|
f.write_str("ExprStmtBlock")?;
|
||||||
f.debug_list().entries(x.iter()).finish()
|
f.debug_list().entries(x.iter()).finish()
|
||||||
}
|
}
|
||||||
Self::FnCall(x, _) => {
|
Self::FnCall(x, _) => fmt::Debug::fmt(x, f),
|
||||||
let mut ff = f.debug_struct("FnCall");
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
x.namespace.as_ref().map(|ns| ff.field("namespace", ns));
|
|
||||||
ff.field("name", &x.name)
|
|
||||||
.field("hash", &x.hashes)
|
|
||||||
.field("args", &x.args);
|
|
||||||
if !x.constants.is_empty() {
|
|
||||||
ff.field("constants", &x.constants);
|
|
||||||
}
|
|
||||||
if x.capture_parent_scope {
|
|
||||||
ff.field("capture_parent_scope", &x.capture_parent_scope);
|
|
||||||
}
|
|
||||||
ff.finish()
|
|
||||||
}
|
|
||||||
Self::Index(x, term, pos) => {
|
Self::Index(x, term, pos) => {
|
||||||
display_pos = *pos;
|
display_pos = *pos;
|
||||||
|
|
||||||
@ -625,6 +632,7 @@ impl Expr {
|
|||||||
args: once(Self::Stack(0, pos)).collect(),
|
args: once(Self::Stack(0, pos)).collect(),
|
||||||
constants: once(f.fn_name().into()).collect(),
|
constants: once(f.fn_name().into()).collect(),
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
|
pos,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
pos,
|
pos,
|
||||||
@ -681,15 +689,30 @@ impl Expr {
|
|||||||
| Self::Map(_, pos)
|
| Self::Map(_, pos)
|
||||||
| Self::Variable(_, pos, _)
|
| Self::Variable(_, pos, _)
|
||||||
| Self::Stack(_, pos)
|
| Self::Stack(_, pos)
|
||||||
| Self::FnCall(_, pos)
|
| Self::And(_, pos)
|
||||||
|
| Self::Or(_, pos)
|
||||||
| Self::Index(_, _, pos)
|
| Self::Index(_, _, pos)
|
||||||
|
| Self::Dot(_, _, pos)
|
||||||
| Self::Custom(_, pos)
|
| Self::Custom(_, pos)
|
||||||
| Self::InterpolatedString(_, pos)
|
| Self::InterpolatedString(_, pos)
|
||||||
| Self::Property(_, pos) => *pos,
|
| Self::Property(_, pos) => *pos,
|
||||||
|
|
||||||
Self::Stmt(x) => x.position(),
|
Self::FnCall(x, _) => x.pos,
|
||||||
|
|
||||||
Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) => x.lhs.position(),
|
Self::Stmt(x) => x.position(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Get the starting [position][Position] of the expression.
|
||||||
|
/// For a binary expression, this will be the left-most LHS instead of the operator.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn start_position(&self) -> Position {
|
||||||
|
match self {
|
||||||
|
Self::And(x, _) | Self::Or(x, _) | Self::Index(x, _, _) | Self::Dot(x, _, _) => {
|
||||||
|
x.lhs.start_position()
|
||||||
|
}
|
||||||
|
Self::FnCall(_, pos) => *pos,
|
||||||
|
_ => self.position(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Override the [position][Position] of the expression.
|
/// Override the [position][Position] of the expression.
|
||||||
@ -718,7 +741,7 @@ impl Expr {
|
|||||||
| Self::InterpolatedString(_, pos)
|
| Self::InterpolatedString(_, pos)
|
||||||
| Self::Property(_, pos) => *pos = new_pos,
|
| Self::Property(_, pos) => *pos = new_pos,
|
||||||
|
|
||||||
Self::Stmt(x) => x.set_position(new_pos),
|
Self::Stmt(x) => x.set_position(new_pos, Position::NONE),
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -11,6 +11,9 @@ pub use ast::{ASTNode, AST};
|
|||||||
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
|
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
|
||||||
pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
|
pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
|
||||||
pub use ident::Ident;
|
pub use ident::Ident;
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub use script_fn::EncapsulatedEnviron;
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
||||||
pub use stmt::{ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, SwitchCases, TryCatchBlock};
|
pub use stmt::{ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, SwitchCases, TryCatchBlock};
|
||||||
|
@ -2,24 +2,43 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
|
|
||||||
use super::{FnAccess, StmtBlock};
|
use super::{FnAccess, StmtBlock};
|
||||||
use crate::{Identifier, Module, Shared, StaticVec};
|
use crate::{Identifier, StaticVec};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{fmt, hash::Hash};
|
use std::{fmt, hash::Hash};
|
||||||
|
|
||||||
|
/// _(internals)_ Encapsulated AST environment.
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
///
|
||||||
|
/// 1) other functions defined within the same AST
|
||||||
|
/// 2) the stack of imported [modules][crate::Module]
|
||||||
|
/// 3) global constants
|
||||||
|
///
|
||||||
|
/// Not available under `no_module` or `no_function`.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct EncapsulatedEnviron {
|
||||||
|
/// Functions defined within the same [`AST`][crate::AST].
|
||||||
|
pub lib: crate::Shared<crate::Module>,
|
||||||
|
/// Imported [modules][crate::Module].
|
||||||
|
pub imports: Box<[(Identifier, crate::Shared<crate::Module>)]>,
|
||||||
|
/// Globally-defined constants.
|
||||||
|
pub constants: Option<crate::eval::GlobalConstants>,
|
||||||
|
}
|
||||||
|
|
||||||
/// _(internals)_ A type containing information on a script-defined function.
|
/// _(internals)_ A type containing information on a script-defined function.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ScriptFnDef {
|
pub struct ScriptFnDef {
|
||||||
/// Function body.
|
/// Function body.
|
||||||
pub body: StmtBlock,
|
pub body: StmtBlock,
|
||||||
/// Encapsulated running environment, if any.
|
/// Encapsulated AST environment, if any.
|
||||||
pub lib: Option<Shared<Module>>,
|
|
||||||
/// Encapsulated stack of imported modules, if any.
|
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module` or `no_function`.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub global: Option<Box<[(Identifier, Shared<Module>)]>>,
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub environ: Option<EncapsulatedEnviron>,
|
||||||
/// Function name.
|
/// Function name.
|
||||||
pub name: Identifier,
|
pub name: Identifier,
|
||||||
/// Function access mode.
|
/// Function access mode.
|
||||||
|
@ -134,7 +134,7 @@ pub struct TryCatchBlock {
|
|||||||
/// _(internals)_ A scoped block of statements.
|
/// _(internals)_ A scoped block of statements.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Clone, Hash, Default)]
|
#[derive(Clone, Hash, Default)]
|
||||||
pub struct StmtBlock(StaticVec<Stmt>, Position);
|
pub struct StmtBlock(StaticVec<Stmt>, (Position, Position));
|
||||||
|
|
||||||
impl StmtBlock {
|
impl StmtBlock {
|
||||||
/// A [`StmtBlock`] that does not exist.
|
/// A [`StmtBlock`] that does not exist.
|
||||||
@ -142,16 +142,20 @@ impl StmtBlock {
|
|||||||
|
|
||||||
/// Create a new [`StmtBlock`].
|
/// Create a new [`StmtBlock`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(statements: impl IntoIterator<Item = Stmt>, pos: Position) -> Self {
|
pub fn new(
|
||||||
|
statements: impl IntoIterator<Item = Stmt>,
|
||||||
|
start_pos: Position,
|
||||||
|
end_pos: Position,
|
||||||
|
) -> Self {
|
||||||
let mut statements: StaticVec<_> = statements.into_iter().collect();
|
let mut statements: StaticVec<_> = statements.into_iter().collect();
|
||||||
statements.shrink_to_fit();
|
statements.shrink_to_fit();
|
||||||
Self(statements, pos)
|
Self(statements, (start_pos, end_pos))
|
||||||
}
|
}
|
||||||
/// Create an empty [`StmtBlock`].
|
/// Create an empty [`StmtBlock`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn empty(pos: Position) -> Self {
|
pub const fn empty(pos: Position) -> Self {
|
||||||
Self(StaticVec::new_const(), pos)
|
Self(StaticVec::new_const(), (pos, pos))
|
||||||
}
|
}
|
||||||
/// Is this statements block empty?
|
/// Is this statements block empty?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -183,16 +187,42 @@ impl StmtBlock {
|
|||||||
pub fn iter(&self) -> impl Iterator<Item = &Stmt> {
|
pub fn iter(&self) -> impl Iterator<Item = &Stmt> {
|
||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
/// Get the position (location of the beginning `{`) of this statements block.
|
/// Get the start position (location of the beginning `{`) of this statements block.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn position(&self) -> Position {
|
pub const fn position(&self) -> Position {
|
||||||
|
(self.1).0
|
||||||
|
}
|
||||||
|
/// Get the end position (location of the ending `}`) of this statements block.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn end_position(&self) -> Position {
|
||||||
|
(self.1).1
|
||||||
|
}
|
||||||
|
/// Get the positions (locations of the beginning `{` and ending `}`) of this statements block.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn positions(&self) -> (Position, Position) {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
/// Set the position (location of the beginning `{`) of this statements block.
|
/// Get the positions (locations of the beginning `{` and ending `}`) of this statements block
|
||||||
|
/// or a default.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_position(&mut self, pos: Position) {
|
#[must_use]
|
||||||
self.1 = pos;
|
pub const fn positions_or_else(
|
||||||
|
&self,
|
||||||
|
def_start_pos: Position,
|
||||||
|
def_end_pos: Position,
|
||||||
|
) -> (Position, Position) {
|
||||||
|
(
|
||||||
|
(self.1).0.or_else(def_start_pos),
|
||||||
|
(self.1).1.or_else(def_end_pos),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/// Set the positions of this statements block.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_position(&mut self, start_pos: Position, end_pos: Position) {
|
||||||
|
self.1 = (start_pos, end_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +260,12 @@ impl fmt::Debug for StmtBlock {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str("Block")?;
|
f.write_str("Block")?;
|
||||||
fmt::Debug::fmt(&self.0, f)?;
|
fmt::Debug::fmt(&self.0, f)?;
|
||||||
self.1.debug_print(f)
|
(self.1).0.debug_print(f)?;
|
||||||
|
#[cfg(not(feature = "no_position"))]
|
||||||
|
if !(self.1).1.is_none() {
|
||||||
|
write!(f, "-{:?}", (self.1).1)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,10 +274,10 @@ impl From<Stmt> for StmtBlock {
|
|||||||
fn from(stmt: Stmt) -> Self {
|
fn from(stmt: Stmt) -> Self {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
|
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
|
||||||
Stmt::Noop(pos) => Self(StaticVec::new_const(), pos),
|
Stmt::Noop(pos) => Self(StaticVec::new_const(), (pos, pos)),
|
||||||
_ => {
|
_ => {
|
||||||
let pos = stmt.position();
|
let pos = stmt.position();
|
||||||
Self(vec![stmt].into(), pos)
|
Self(vec![stmt].into(), (pos, Position::NONE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,7 +344,7 @@ pub enum Stmt {
|
|||||||
/// function call forming one statement.
|
/// function call forming one statement.
|
||||||
FnCall(Box<FnCallExpr>, Position),
|
FnCall(Box<FnCallExpr>, Position),
|
||||||
/// `{` stmt`;` ... `}`
|
/// `{` stmt`;` ... `}`
|
||||||
Block(Box<[Stmt]>, Position),
|
Block(Box<[Stmt]>, (Position, Position)),
|
||||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||||
TryCatch(Box<TryCatchBlock>, Position),
|
TryCatch(Box<TryCatchBlock>, Position),
|
||||||
/// [expression][Expr]
|
/// [expression][Expr]
|
||||||
@ -377,7 +412,7 @@ impl Stmt {
|
|||||||
match self {
|
match self {
|
||||||
Self::Noop(pos)
|
Self::Noop(pos)
|
||||||
| Self::BreakLoop(_, pos)
|
| Self::BreakLoop(_, pos)
|
||||||
| Self::Block(_, pos)
|
| Self::Block(_, (pos, _))
|
||||||
| Self::Assignment(_, pos)
|
| Self::Assignment(_, pos)
|
||||||
| Self::FnCall(_, pos)
|
| Self::FnCall(_, pos)
|
||||||
| Self::If(_, _, pos)
|
| Self::If(_, _, pos)
|
||||||
@ -389,7 +424,7 @@ impl Stmt {
|
|||||||
| Self::Var(_, _, _, pos)
|
| Self::Var(_, _, _, pos)
|
||||||
| Self::TryCatch(_, pos) => *pos,
|
| Self::TryCatch(_, pos) => *pos,
|
||||||
|
|
||||||
Self::Expr(x) => x.position(),
|
Self::Expr(x) => x.start_position(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Self::Import(_, _, pos) => *pos,
|
Self::Import(_, _, pos) => *pos,
|
||||||
@ -405,7 +440,7 @@ impl Stmt {
|
|||||||
match self {
|
match self {
|
||||||
Self::Noop(pos)
|
Self::Noop(pos)
|
||||||
| Self::BreakLoop(_, pos)
|
| Self::BreakLoop(_, pos)
|
||||||
| Self::Block(_, pos)
|
| Self::Block(_, (pos, _))
|
||||||
| Self::Assignment(_, pos)
|
| Self::Assignment(_, pos)
|
||||||
| Self::FnCall(_, pos)
|
| Self::FnCall(_, pos)
|
||||||
| Self::If(_, _, pos)
|
| Self::If(_, _, pos)
|
||||||
|
@ -9,32 +9,29 @@ Tools for running Rhai scripts.
|
|||||||
| [`rhai-repl`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-repl.rs) | `rustyline` | simple REPL that interactively evaluates statements |
|
| [`rhai-repl`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-repl.rs) | `rustyline` | simple REPL that interactively evaluates statements |
|
||||||
| [`rhai-dbg`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-dbg.rs) | `debugging` | the _Rhai Debugger_ |
|
| [`rhai-dbg`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-dbg.rs) | `debugging` | the _Rhai Debugger_ |
|
||||||
|
|
||||||
|
There is a feature called `bin-features` which automatically includes all the necessary features
|
||||||
|
required for building these tools.
|
||||||
|
|
||||||
|
|
||||||
How to Run
|
How to Run
|
||||||
----------
|
----------
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run --bin sample_app_to_run
|
cargo run --features bin-features --bin sample_app_to_run
|
||||||
```
|
|
||||||
|
|
||||||
or with required features
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo run --bin sample_app_to_run --features feature1,feature2,feature3
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
How to Install
|
How to Install
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
To install these all tools (with [`decimal`] and [`metadata`] support), use the following command:
|
To install these all tools (with full features), use the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install --path . --bins --features decimal,metadata,debugging,rustyline
|
cargo install --path . --bins --features bin-features
|
||||||
```
|
```
|
||||||
|
|
||||||
or specifically:
|
or specifically:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install --path . --bin rhai-run --features decimal,metadata,debugging,rustyline
|
cargo install --path . --bin rhai-run --features bin-features
|
||||||
```
|
```
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::debugger::DebuggerCommand;
|
use rhai::debugger::{BreakPoint, DebuggerCommand, DebuggerEvent};
|
||||||
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
|
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
@ -36,6 +36,32 @@ fn print_source(lines: &[String], pos: Position, offset: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_current_source(
|
||||||
|
context: &mut rhai::EvalContext,
|
||||||
|
source: Option<&str>,
|
||||||
|
pos: Position,
|
||||||
|
lines: &Vec<String>,
|
||||||
|
) {
|
||||||
|
let current_source = &mut *context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.state_mut()
|
||||||
|
.write_lock::<ImmutableString>()
|
||||||
|
.unwrap();
|
||||||
|
let src = source.unwrap_or("");
|
||||||
|
if src != current_source {
|
||||||
|
println!(">>> Source => {}", source.unwrap_or("main script"));
|
||||||
|
*current_source = src.into();
|
||||||
|
}
|
||||||
|
if !src.is_empty() {
|
||||||
|
// Print just a line number for imported modules
|
||||||
|
println!("{} @ {:?}", src, pos);
|
||||||
|
} else {
|
||||||
|
// Print the current source line
|
||||||
|
print_source(lines, pos, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pretty-print error.
|
/// Pretty-print error.
|
||||||
fn print_error(input: &str, mut err: EvalAltResult) {
|
fn print_error(input: &str, mut err: EvalAltResult) {
|
||||||
let lines: Vec<_> = input.trim().split('\n').collect();
|
let lines: Vec<_> = input.trim().split('\n').collect();
|
||||||
@ -71,36 +97,38 @@ fn print_error(input: &str, mut err: EvalAltResult) {
|
|||||||
|
|
||||||
/// Print debug help.
|
/// Print debug help.
|
||||||
fn print_debug_help() {
|
fn print_debug_help() {
|
||||||
println!("help => print this help");
|
println!("help, h => print this help");
|
||||||
println!("quit, exit, kill => quit");
|
println!("quit, q, exit, kill => quit");
|
||||||
println!("scope => print the scope");
|
println!("scope => print the scope");
|
||||||
println!("print => print all variables de-duplicated");
|
println!("print, p => print all variables de-duplicated");
|
||||||
println!("print <variable> => print the current value of a variable");
|
println!("print/p <variable> => print the current value of a variable");
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
println!("imports => print all imported modules");
|
println!("imports => print all imported modules");
|
||||||
println!("node => print the current AST node");
|
println!("node => print the current AST node");
|
||||||
println!("backtrace => print the current call-stack");
|
println!("list, l => print the current source line");
|
||||||
println!("breakpoints => print all break-points");
|
println!("backtrace, bt => print the current call-stack");
|
||||||
println!("enable <bp#> => enable a break-point");
|
println!("info break, i b => print all break-points");
|
||||||
println!("disable <bp#> => disable a break-point");
|
println!("enable/en <bp#> => enable a break-point");
|
||||||
println!("delete <bp#> => delete a break-point");
|
println!("disable/dis <bp#> => disable a break-point");
|
||||||
println!("clear => delete all break-points");
|
println!("delete, d => delete all break-points");
|
||||||
|
println!("delete/d <bp#> => delete a break-point");
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
println!("break => set a new break-point at the current position");
|
println!("break, b => set a new break-point at the current position");
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
println!("break <line#> => set a new break-point at a line number");
|
println!("break/b <line#> => set a new break-point at a line number");
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
println!("break .<prop> => set a new break-point for a property access");
|
println!("break/b .<prop> => set a new break-point for a property access");
|
||||||
println!("break <func> => set a new break-point for a function call");
|
println!("break/b <func> => set a new break-point for a function call");
|
||||||
println!(
|
println!(
|
||||||
"break <func> <#args> => set a new break-point for a function call with #args arguments"
|
"break/b <func> <#args> => set a new break-point for a function call with #args arguments"
|
||||||
);
|
);
|
||||||
println!("throw [message] => throw an exception (message optional)");
|
println!("throw [message] => throw an exception (message optional)");
|
||||||
println!("run => restart the script evaluation from beginning");
|
println!("run, r => restart the script evaluation from beginning");
|
||||||
println!("step => go to the next expression, diving into functions");
|
println!("step, s => go to the next expression, diving into functions");
|
||||||
println!("over => go to the next expression, skipping oer functions");
|
println!("over => go to the next expression, skipping oer functions");
|
||||||
println!("next => go to the next statement, skipping over functions");
|
println!("next, n, <Enter> => go to the next statement, skipping over functions");
|
||||||
println!("continue => continue normal execution");
|
println!("finish, f => continue until the end of the current function call");
|
||||||
|
println!("continue, c => continue normal execution");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,36 +248,58 @@ fn main() {
|
|||||||
// Hook up debugger
|
// Hook up debugger
|
||||||
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
|
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
engine.on_debugger(
|
engine.register_debugger(
|
||||||
// Store the current source in the debugger state
|
// Store the current source in the debugger state
|
||||||
|| "".into(),
|
|| "".into(),
|
||||||
// Main debugging interface
|
// Main debugging interface
|
||||||
move |context, node, source, pos| {
|
move |context, event, node, source, pos| {
|
||||||
{
|
match event {
|
||||||
let current_source = &mut *context
|
DebuggerEvent::Step => (),
|
||||||
.global_runtime_state_mut()
|
DebuggerEvent::BreakPoint(n) => {
|
||||||
.debugger
|
match context.global_runtime_state().debugger.break_points()[n] {
|
||||||
.state_mut()
|
#[cfg(not(feature = "no_position"))]
|
||||||
.write_lock::<ImmutableString>()
|
BreakPoint::AtPosition { .. } => (),
|
||||||
.unwrap();
|
BreakPoint::AtFunctionName { ref name, .. }
|
||||||
|
| BreakPoint::AtFunctionCall { ref name, .. } => {
|
||||||
let src = source.unwrap_or("");
|
println!("! Call to function {}.", name)
|
||||||
|
}
|
||||||
// Check source
|
#[cfg(not(feature = "no_object"))]
|
||||||
if src != current_source {
|
BreakPoint::AtProperty { ref name, .. } => {
|
||||||
println!(">>> Source => {}", source.unwrap_or("main script"));
|
println!("! Property {} accessed.", name)
|
||||||
*current_source = src.into();
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
DebuggerEvent::FunctionExitWithValue(r) => {
|
||||||
if !src.is_empty() {
|
println!(
|
||||||
// Print just a line number for imported modules
|
"! Return from function call '{}' => {}",
|
||||||
println!("{} @ {:?}", src, pos);
|
context
|
||||||
} else {
|
.global_runtime_state()
|
||||||
// Print the current source line
|
.debugger
|
||||||
print_source(&lines, pos, 0);
|
.call_stack()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.fn_name,
|
||||||
|
r
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DebuggerEvent::FunctionExitWithError(err) => {
|
||||||
|
println!(
|
||||||
|
"! Return from function call '{}' with error: {}",
|
||||||
|
context
|
||||||
|
.global_runtime_state()
|
||||||
|
.debugger
|
||||||
|
.call_stack()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.fn_name,
|
||||||
|
err
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print current source line
|
||||||
|
print_current_source(context, source, pos, &lines);
|
||||||
|
|
||||||
// Read stdin for commands
|
// Read stdin for commands
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
|
||||||
@ -267,8 +317,8 @@ fn main() {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.as_slice()
|
.as_slice()
|
||||||
{
|
{
|
||||||
["help", ..] => print_debug_help(),
|
["help" | "h", ..] => print_debug_help(),
|
||||||
["exit", ..] | ["quit", ..] | ["kill", ..] => {
|
["exit" | "quit" | "q" | "kill", ..] => {
|
||||||
println!("Script terminated. Bye!");
|
println!("Script terminated. Bye!");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
@ -276,12 +326,14 @@ fn main() {
|
|||||||
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
|
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
["continue", ..] => break Ok(DebuggerCommand::Continue),
|
["list" | "l", ..] => print_current_source(context, source, pos, &lines),
|
||||||
[] | ["step", ..] => break Ok(DebuggerCommand::StepInto),
|
["continue" | "c", ..] => break Ok(DebuggerCommand::Continue),
|
||||||
|
["finish" | "f", ..] => break Ok(DebuggerCommand::FunctionExit),
|
||||||
|
[] | ["step" | "s", ..] => break Ok(DebuggerCommand::StepInto),
|
||||||
["over", ..] => break Ok(DebuggerCommand::StepOver),
|
["over", ..] => break Ok(DebuggerCommand::StepOver),
|
||||||
["next", ..] => break Ok(DebuggerCommand::Next),
|
["next" | "n", ..] => break Ok(DebuggerCommand::Next),
|
||||||
["scope", ..] => print_scope(context.scope(), false),
|
["scope", ..] => print_scope(context.scope(), false),
|
||||||
["print", var_name, ..] => {
|
["print" | "p", var_name, ..] => {
|
||||||
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
|
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
|
||||||
if value.is::<()>() {
|
if value.is::<()>() {
|
||||||
println!("=> ()");
|
println!("=> ()");
|
||||||
@ -292,7 +344,7 @@ fn main() {
|
|||||||
eprintln!("Variable not found: {}", var_name);
|
eprintln!("Variable not found: {}", var_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["print", ..] => print_scope(context.scope(), true),
|
["print" | "p"] => print_scope(context.scope(), true),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
["imports", ..] => {
|
["imports", ..] => {
|
||||||
for (i, (name, module)) in context
|
for (i, (name, module)) in context
|
||||||
@ -311,7 +363,7 @@ fn main() {
|
|||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
["backtrace", ..] => {
|
["backtrace" | "bt", ..] => {
|
||||||
for frame in context
|
for frame in context
|
||||||
.global_runtime_state()
|
.global_runtime_state()
|
||||||
.debugger
|
.debugger
|
||||||
@ -322,15 +374,7 @@ fn main() {
|
|||||||
println!("{}", frame)
|
println!("{}", frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["clear", ..] => {
|
["info", "break", ..] | ["i", "b", ..] => Iterator::for_each(
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.clear();
|
|
||||||
println!("All break-points cleared.");
|
|
||||||
}
|
|
||||||
["breakpoints", ..] => Iterator::for_each(
|
|
||||||
context
|
context
|
||||||
.global_runtime_state()
|
.global_runtime_state()
|
||||||
.debugger
|
.debugger
|
||||||
@ -347,7 +391,7 @@ fn main() {
|
|||||||
_ => println!("[{}] {}", i + 1, bp),
|
_ => println!("[{}] {}", i + 1, bp),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
["enable", n, ..] => {
|
["enable" | "en", n, ..] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -370,7 +414,7 @@ fn main() {
|
|||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("Invalid break-point: '{}'", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["disable", n, ..] => {
|
["disable" | "dis", n, ..] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -393,7 +437,7 @@ fn main() {
|
|||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("Invalid break-point: '{}'", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["delete", n, ..] => {
|
["delete" | "d", n, ..] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -414,7 +458,15 @@ fn main() {
|
|||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("Invalid break-point: '{}'", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["break", fn_name, args, ..] => {
|
["delete" | "d", ..] => {
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.clear();
|
||||||
|
println!("All break-points deleted.");
|
||||||
|
}
|
||||||
|
["break" | "b", fn_name, args, ..] => {
|
||||||
if let Ok(args) = args.parse::<usize>() {
|
if let Ok(args) = args.parse::<usize>() {
|
||||||
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
||||||
name: fn_name.trim().into(),
|
name: fn_name.trim().into(),
|
||||||
@ -433,7 +485,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
// Property name
|
// Property name
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
["break", param] if param.starts_with('.') && param.len() > 1 => {
|
["break" | "b", param] if param.starts_with('.') && param.len() > 1 => {
|
||||||
let bp = rhai::debugger::BreakPoint::AtProperty {
|
let bp = rhai::debugger::BreakPoint::AtProperty {
|
||||||
name: param[1..].into(),
|
name: param[1..].into(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -447,7 +499,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
// Numeric parameter
|
// Numeric parameter
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
["break", param] if param.parse::<usize>().is_ok() => {
|
["break" | "b", param] if param.parse::<usize>().is_ok() => {
|
||||||
let n = param.parse::<usize>().unwrap();
|
let n = param.parse::<usize>().unwrap();
|
||||||
let range = if source.is_none() {
|
let range = if source.is_none() {
|
||||||
1..=lines.len()
|
1..=lines.len()
|
||||||
@ -472,7 +524,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Function name parameter
|
// Function name parameter
|
||||||
["break", param] => {
|
["break" | "b", param] => {
|
||||||
let bp = rhai::debugger::BreakPoint::AtFunctionName {
|
let bp = rhai::debugger::BreakPoint::AtFunctionName {
|
||||||
name: param.trim().into(),
|
name: param.trim().into(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -485,7 +537,7 @@ fn main() {
|
|||||||
.push(bp);
|
.push(bp);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
["break", ..] => {
|
["break" | "b"] => {
|
||||||
let bp = rhai::debugger::BreakPoint::AtPosition {
|
let bp = rhai::debugger::BreakPoint::AtPosition {
|
||||||
source: source.unwrap_or("").into(),
|
source: source.unwrap_or("").into(),
|
||||||
pos,
|
pos,
|
||||||
@ -505,7 +557,7 @@ fn main() {
|
|||||||
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
||||||
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
||||||
}
|
}
|
||||||
["run", ..] => {
|
["run" | "r", ..] => {
|
||||||
println!("Restarting script...");
|
println!("Restarting script...");
|
||||||
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
||||||
}
|
}
|
||||||
|
@ -44,9 +44,13 @@ fn print_error(input: &str, mut err: EvalAltResult) {
|
|||||||
/// Print help text.
|
/// Print help text.
|
||||||
fn print_help() {
|
fn print_help() {
|
||||||
println!("help => print this help");
|
println!("help => print this help");
|
||||||
println!("keys => print list of key bindings");
|
|
||||||
println!("quit, exit => quit");
|
println!("quit, exit => quit");
|
||||||
|
println!("keys => print list of key bindings");
|
||||||
println!("history => print lines history");
|
println!("history => print lines history");
|
||||||
|
println!("!! => repeat the last history line");
|
||||||
|
println!("!<#> => repeat a particular history line");
|
||||||
|
println!("!<text> => repeat the last history line starting with some text");
|
||||||
|
println!("!?<text> => repeat the last history line containing some text");
|
||||||
println!("scope => print all variables in the scope");
|
println!("scope => print all variables in the scope");
|
||||||
println!("strict => toggle on/off Strict Variables Mode");
|
println!("strict => toggle on/off Strict Variables Mode");
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
@ -295,6 +299,7 @@ fn main() {
|
|||||||
engine.set_module_resolver(resolver);
|
engine.set_module_resolver(resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register sample functions
|
||||||
engine
|
engine
|
||||||
.register_fn("test", |x: INT, y: INT| format!("{} {}", x, y))
|
.register_fn("test", |x: INT, y: INT| format!("{} {}", x, y))
|
||||||
.register_fn("test", |x: &mut INT, y: INT, z: &str| {
|
.register_fn("test", |x: &mut INT, y: INT, z: &str| {
|
||||||
@ -310,6 +315,10 @@ fn main() {
|
|||||||
|
|
||||||
// REPL loop
|
// REPL loop
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
let mut replacement = None;
|
||||||
|
let mut replacement_index = 0;
|
||||||
|
let mut history_offset = 1;
|
||||||
|
|
||||||
let mut main_ast = AST::empty();
|
let mut main_ast = AST::empty();
|
||||||
let mut ast_u = AST::empty();
|
let mut ast_u = AST::empty();
|
||||||
let mut ast = AST::empty();
|
let mut ast = AST::empty();
|
||||||
@ -317,35 +326,53 @@ fn main() {
|
|||||||
print_help();
|
print_help();
|
||||||
|
|
||||||
'main_loop: loop {
|
'main_loop: loop {
|
||||||
input.clear();
|
if let Some(replace) = replacement.take() {
|
||||||
|
input = replace;
|
||||||
loop {
|
if rl.add_history_entry(input.clone()) {
|
||||||
let prompt = if input.is_empty() {
|
history_offset += 1;
|
||||||
"rhai-repl> "
|
}
|
||||||
|
if input.contains('\n') {
|
||||||
|
println!("[{}] ~~~~", replacement_index);
|
||||||
|
println!("{}", input);
|
||||||
|
println!("~~~~");
|
||||||
} else {
|
} else {
|
||||||
" > "
|
println!("[{}] {}", replacement_index, input);
|
||||||
};
|
}
|
||||||
|
replacement_index = 0;
|
||||||
|
} else {
|
||||||
|
input.clear();
|
||||||
|
|
||||||
match rl.readline(prompt) {
|
loop {
|
||||||
// Line continuation
|
let prompt = if input.is_empty() {
|
||||||
Ok(mut line) if line.ends_with("\\") => {
|
"rhai-repl> "
|
||||||
line.pop();
|
} else {
|
||||||
input += line.trim_end();
|
" > "
|
||||||
input.push('\n');
|
};
|
||||||
}
|
|
||||||
Ok(line) => {
|
match rl.readline(prompt) {
|
||||||
input += line.trim_end();
|
// Line continuation
|
||||||
if !input.is_empty() {
|
Ok(mut line) if line.ends_with("\\") => {
|
||||||
rl.add_history_entry(input.clone());
|
line.pop();
|
||||||
|
input += line.trim_end();
|
||||||
|
input.push('\n');
|
||||||
|
}
|
||||||
|
Ok(line) => {
|
||||||
|
input += line.trim_end();
|
||||||
|
if !input.is_empty() && !input.starts_with('!') && input.trim() != "history"
|
||||||
|
{
|
||||||
|
if rl.add_history_entry(input.clone()) {
|
||||||
|
history_offset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'main_loop,
|
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'main_loop,
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error: {:?}", err);
|
eprintln!("Error: {:?}", err);
|
||||||
break 'main_loop;
|
break 'main_loop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,10 +397,10 @@ fn main() {
|
|||||||
"history" => {
|
"history" => {
|
||||||
for (i, h) in rl.history().iter().enumerate() {
|
for (i, h) in rl.history().iter().enumerate() {
|
||||||
match &h.split('\n').collect::<Vec<_>>()[..] {
|
match &h.split('\n').collect::<Vec<_>>()[..] {
|
||||||
[line] => println!("[{}] {}", i + 1, line),
|
[line] => println!("[{}] {}", history_offset + i, line),
|
||||||
lines => {
|
lines => {
|
||||||
for (x, line) in lines.iter().enumerate() {
|
for (x, line) in lines.iter().enumerate() {
|
||||||
let number = format!("[{}]", i + 1);
|
let number = format!("[{}]", history_offset + i);
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
println!("{} {}", number, line.trim_end());
|
println!("{} {}", number, line.trim_end());
|
||||||
} else {
|
} else {
|
||||||
@ -446,6 +473,57 @@ fn main() {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
"!!" => {
|
||||||
|
if let Some(line) = rl.history().last() {
|
||||||
|
replacement = Some(line.clone());
|
||||||
|
replacement_index = history_offset + rl.history().len() - 1;
|
||||||
|
} else {
|
||||||
|
eprintln!("No lines history!");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ if script.starts_with("!?") => {
|
||||||
|
let text = script[2..].trim();
|
||||||
|
if let Some((n, line)) = rl
|
||||||
|
.history()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.enumerate()
|
||||||
|
.find(|&(_, h)| h.contains(text))
|
||||||
|
{
|
||||||
|
replacement = Some(line.clone());
|
||||||
|
replacement_index = history_offset + (rl.history().len() - 1 - n);
|
||||||
|
} else {
|
||||||
|
eprintln!("History line not found: {}", text);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ if script.starts_with('!') => {
|
||||||
|
if let Ok(num) = script[1..].parse::<usize>() {
|
||||||
|
if num >= history_offset {
|
||||||
|
if let Some(line) = rl.history().get(num - history_offset) {
|
||||||
|
replacement = Some(line.clone());
|
||||||
|
replacement_index = num;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let prefix = script[1..].trim();
|
||||||
|
if let Some((n, line)) = rl
|
||||||
|
.history()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.enumerate()
|
||||||
|
.find(|&(_, h)| h.trim_start().starts_with(prefix))
|
||||||
|
{
|
||||||
|
replacement = Some(line.clone());
|
||||||
|
replacement_index = history_offset + (rl.history().len() - 1 - n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln!("History line not found: {}", &script[1..]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
//! Main module defining the script evaluation [`Engine`].
|
//! Main module defining the script evaluation [`Engine`].
|
||||||
|
|
||||||
use crate::api::custom_syntax::CustomSyntax;
|
use crate::api::custom_syntax::CustomSyntax;
|
||||||
use crate::func::native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback};
|
use crate::func::native::{
|
||||||
|
OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback,
|
||||||
|
};
|
||||||
use crate::packages::{Package, StandardPackage};
|
use crate::packages::{Package, StandardPackage};
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
use crate::types::dynamic::{map_std_type_name, Union};
|
use crate::types::dynamic::Union;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Identifier, ImmutableString, Module, Position, RhaiError, RhaiResult, Shared,
|
Dynamic, Identifier, ImmutableString, Module, Position, RhaiResult, Shared, StaticVec,
|
||||||
StaticVec, ERR,
|
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
any::type_name,
|
|
||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
fmt,
|
fmt,
|
||||||
num::NonZeroU8,
|
num::NonZeroU8,
|
||||||
@ -115,6 +115,8 @@ pub struct Engine {
|
|||||||
pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
|
pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
|
||||||
/// Custom syntax.
|
/// Custom syntax.
|
||||||
pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
|
pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
|
||||||
|
/// Callback closure for filtering variable definition.
|
||||||
|
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,
|
||||||
/// Callback closure for resolving variable access.
|
/// Callback closure for resolving variable access.
|
||||||
pub(crate) resolve_var: Option<Box<OnVarCallback>>,
|
pub(crate) resolve_var: Option<Box<OnVarCallback>>,
|
||||||
/// Callback closure to remap tokens during parsing.
|
/// Callback closure to remap tokens during parsing.
|
||||||
@ -162,6 +164,7 @@ impl fmt::Debug for Engine {
|
|||||||
.field("disabled_symbols", &self.disabled_symbols)
|
.field("disabled_symbols", &self.disabled_symbols)
|
||||||
.field("custom_keywords", &self.custom_keywords)
|
.field("custom_keywords", &self.custom_keywords)
|
||||||
.field("custom_syntax", &(!self.custom_syntax.is_empty()))
|
.field("custom_syntax", &(!self.custom_syntax.is_empty()))
|
||||||
|
.field("def_var_filter", &self.def_var_filter.is_some())
|
||||||
.field("resolve_var", &self.resolve_var.is_some())
|
.field("resolve_var", &self.resolve_var.is_some())
|
||||||
.field("token_mapper", &self.token_mapper.is_some())
|
.field("token_mapper", &self.token_mapper.is_some())
|
||||||
.field("print", &self.print.is_some())
|
.field("print", &self.print.is_some())
|
||||||
@ -274,6 +277,7 @@ impl Engine {
|
|||||||
custom_keywords: BTreeMap::new(),
|
custom_keywords: BTreeMap::new(),
|
||||||
custom_syntax: BTreeMap::new(),
|
custom_syntax: BTreeMap::new(),
|
||||||
|
|
||||||
|
def_var_filter: None,
|
||||||
resolve_var: None,
|
resolve_var: None,
|
||||||
token_mapper: None,
|
token_mapper: None,
|
||||||
|
|
||||||
@ -337,59 +341,4 @@ impl Engine {
|
|||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pretty-print a type name.
|
|
||||||
///
|
|
||||||
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
|
|
||||||
/// the type name provided for the registration will be used.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the type name is `&mut`.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
|
||||||
self.type_names
|
|
||||||
.get(name)
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or_else(|| map_std_type_name(name, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format a type name.
|
|
||||||
///
|
|
||||||
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
|
|
||||||
/// the type name provided for the registration will be used.
|
|
||||||
#[cfg(feature = "metadata")]
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn format_type_name<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> {
|
|
||||||
if name.starts_with("&mut ") {
|
|
||||||
let x = &name[5..];
|
|
||||||
let r = self.format_type_name(x);
|
|
||||||
return if x != r {
|
|
||||||
format!("&mut {}", r).into()
|
|
||||||
} else {
|
|
||||||
name.into()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
self.type_names
|
|
||||||
.get(name)
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or_else(|| match name {
|
|
||||||
"INT" => return type_name::<crate::INT>(),
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
"FLOAT" => return type_name::<crate::FLOAT>(),
|
|
||||||
_ => map_std_type_name(name, false),
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make a `Box<`[`EvalAltResult<ErrorMismatchDataType>`][ERR::ErrorMismatchDataType]`>`.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn make_type_mismatch_err<T>(&self, typ: &str, pos: Position) -> RhaiError {
|
|
||||||
ERR::ErrorMismatchDataType(self.map_type_name(type_name::<T>()).into(), typ.into(), pos)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ impl Engine {
|
|||||||
match chain_type {
|
match chain_type {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
ChainType::Indexing => {
|
ChainType::Indexing => {
|
||||||
let pos = rhs.position();
|
let pos = rhs.start_position();
|
||||||
let root_pos = idx_val.position();
|
let root_pos = idx_val.position();
|
||||||
let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
|
let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ impl Engine {
|
|||||||
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
||||||
|
|
||||||
let mut idx_val_for_setter = idx_val.clone();
|
let mut idx_val_for_setter = idx_val.clone();
|
||||||
let idx_pos = x.lhs.position();
|
let idx_pos = x.lhs.start_position();
|
||||||
let rhs_chain = rhs.into();
|
let rhs_chain = rhs.into();
|
||||||
|
|
||||||
let (try_setter, result) = {
|
let (try_setter, result) = {
|
||||||
@ -189,8 +189,8 @@ impl Engine {
|
|||||||
let fn_name = crate::engine::FN_IDX_SET;
|
let fn_name = crate::engine::FN_IDX_SET;
|
||||||
|
|
||||||
if let Err(err) = self.exec_fn_call(
|
if let Err(err) = self.exec_fn_call(
|
||||||
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
|
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
||||||
root_pos, None, level,
|
true, root_pos, level,
|
||||||
) {
|
) {
|
||||||
// Just ignore if there is no index setter
|
// Just ignore if there is no index setter
|
||||||
if !matches!(*err, ERR::ErrorFunctionNotFound(_, _)) {
|
if !matches!(*err, ERR::ErrorFunctionNotFound(_, _)) {
|
||||||
@ -216,6 +216,7 @@ impl Engine {
|
|||||||
Ok(ref mut obj_ptr) => {
|
Ok(ref mut obj_ptr) => {
|
||||||
self.eval_op_assignment(
|
self.eval_op_assignment(
|
||||||
global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
||||||
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(new_pos))?;
|
.map_err(|err| err.fill_position(new_pos))?;
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -239,8 +240,8 @@ impl Engine {
|
|||||||
let fn_name = crate::engine::FN_IDX_SET;
|
let fn_name = crate::engine::FN_IDX_SET;
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
|
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
||||||
root_pos, None, level,
|
true, root_pos, level,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,6 +303,7 @@ impl Engine {
|
|||||||
)?;
|
)?;
|
||||||
self.eval_op_assignment(
|
self.eval_op_assignment(
|
||||||
global, state, lib, op_info, op_pos, val_target, root, new_val,
|
global, state, lib, op_info, op_pos, val_target, root, new_val,
|
||||||
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(new_pos))?;
|
.map_err(|err| err.fill_position(new_pos))?;
|
||||||
}
|
}
|
||||||
@ -333,8 +335,8 @@ impl Engine {
|
|||||||
let args = &mut [target.as_mut()];
|
let args = &mut [target.as_mut()];
|
||||||
let (mut orig_val, _) = self
|
let (mut orig_val, _) = self
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
global, state, lib, getter, hash, args, is_ref_mut, true, *pos,
|
None, global, state, lib, getter, hash, args, is_ref_mut, true,
|
||||||
None, level,
|
*pos, level,
|
||||||
)
|
)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
@ -364,6 +366,7 @@ impl Engine {
|
|||||||
&mut (&mut orig_val).into(),
|
&mut (&mut orig_val).into(),
|
||||||
root,
|
root,
|
||||||
new_val,
|
new_val,
|
||||||
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(new_pos))?;
|
.map_err(|err| err.fill_position(new_pos))?;
|
||||||
|
|
||||||
@ -373,7 +376,7 @@ impl Engine {
|
|||||||
let hash = crate::ast::FnCallHashes::from_native(*hash_set);
|
let hash = crate::ast::FnCallHashes::from_native(*hash_set);
|
||||||
let args = &mut [target.as_mut(), &mut new_val];
|
let args = &mut [target.as_mut(), &mut new_val];
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, setter, hash, args, is_ref_mut, true, *pos, None,
|
None, global, state, lib, setter, hash, args, is_ref_mut, true, *pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
@ -386,8 +389,8 @@ impl Engine {
|
|||||||
let pos = Position::NONE;
|
let pos = Position::NONE;
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
|
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
||||||
pos, None, level,
|
true, pos, level,
|
||||||
)
|
)
|
||||||
.map_err(
|
.map_err(
|
||||||
|idx_err| match *idx_err {
|
|idx_err| match *idx_err {
|
||||||
@ -408,7 +411,7 @@ impl Engine {
|
|||||||
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
|
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
|
||||||
let args = &mut [target.as_mut()];
|
let args = &mut [target.as_mut()];
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, getter, hash, args, is_ref_mut, true, *pos, None,
|
None, global, state, lib, getter, hash, args, is_ref_mut, true, *pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
@ -508,8 +511,8 @@ impl Engine {
|
|||||||
// Assume getters are always pure
|
// Assume getters are always pure
|
||||||
let (mut val, _) = self
|
let (mut val, _) = self
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
global, state, lib, getter, hash_get, args, is_ref_mut,
|
None, global, state, lib, getter, hash_get, args,
|
||||||
true, pos, None, level,
|
is_ref_mut, true, pos, level,
|
||||||
)
|
)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
@ -556,8 +559,8 @@ impl Engine {
|
|||||||
let mut arg_values = [target.as_mut(), val];
|
let mut arg_values = [target.as_mut(), val];
|
||||||
let args = &mut arg_values;
|
let args = &mut arg_values;
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, setter, hash_set, args, is_ref_mut,
|
None, global, state, lib, setter, hash_set, args,
|
||||||
true, pos, None, level,
|
is_ref_mut, true, pos, level,
|
||||||
)
|
)
|
||||||
.or_else(
|
.or_else(
|
||||||
|err| match *err {
|
|err| match *err {
|
||||||
@ -571,8 +574,8 @@ impl Engine {
|
|||||||
global.hash_idx_set(),
|
global.hash_idx_set(),
|
||||||
);
|
);
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, fn_name, hash_set, args,
|
None, global, state, lib, fn_name, hash_set,
|
||||||
is_ref_mut, true, pos, None, level,
|
args, is_ref_mut, true, pos, level,
|
||||||
)
|
)
|
||||||
.or_else(|idx_err| match *idx_err {
|
.or_else(|idx_err| match *idx_err {
|
||||||
ERR::ErrorIndexingType(_, _) => {
|
ERR::ErrorIndexingType(_, _) => {
|
||||||
@ -626,7 +629,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Syntax error
|
// Syntax error
|
||||||
_ => Err(ERR::ErrorDotExpr("".into(), rhs.position()).into()),
|
_ => Err(ERR::ErrorDotExpr("".into(), rhs.start_position()).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -670,7 +673,7 @@ impl Engine {
|
|||||||
self.inc_operations(&mut global.num_operations, *var_pos)?;
|
self.inc_operations(&mut global.num_operations, *var_pos)?;
|
||||||
|
|
||||||
let (mut target, _) =
|
let (mut target, _) =
|
||||||
self.search_namespace(scope, global, state, lib, this_ptr, lhs)?;
|
self.search_namespace(scope, global, state, lib, this_ptr, lhs, level)?;
|
||||||
|
|
||||||
let obj_ptr = &mut target;
|
let obj_ptr = &mut target;
|
||||||
let root = (x.2.as_str(), *var_pos);
|
let root = (x.2.as_str(), *var_pos);
|
||||||
@ -688,7 +691,7 @@ impl Engine {
|
|||||||
expr => {
|
expr => {
|
||||||
let value = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?;
|
let value = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?;
|
||||||
let obj_ptr = &mut value.into();
|
let obj_ptr = &mut value.into();
|
||||||
let root = ("", expr.position());
|
let root = ("", expr.start_position());
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
|
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
|
||||||
chain_type, level, new_val,
|
chain_type, level, new_val,
|
||||||
@ -732,7 +735,7 @@ impl Engine {
|
|||||||
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|
||||||
|(mut values, mut pos), expr| -> RhaiResultOf<_> {
|
|(mut values, mut pos), expr| -> RhaiResultOf<_> {
|
||||||
let (value, arg_pos) = self.get_arg_value(
|
let (value, arg_pos) = self.get_arg_value(
|
||||||
scope, global, state, lib, this_ptr, level, expr, constants,
|
scope, global, state, lib, this_ptr, expr, constants, level,
|
||||||
)?;
|
)?;
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
pos = arg_pos;
|
pos = arg_pos;
|
||||||
@ -778,7 +781,7 @@ impl Engine {
|
|||||||
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|
||||||
|(mut values, mut pos), expr| -> RhaiResultOf<_> {
|
|(mut values, mut pos), expr| -> RhaiResultOf<_> {
|
||||||
let (value, arg_pos) = self.get_arg_value(
|
let (value, arg_pos) = self.get_arg_value(
|
||||||
scope, global, state, lib, this_ptr, level, expr, constants,
|
scope, global, state, lib, this_ptr, expr, constants, level,
|
||||||
)?;
|
)?;
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
pos = arg_pos
|
pos = arg_pos
|
||||||
@ -801,7 +804,10 @@ impl Engine {
|
|||||||
_ if _parent_chain_type == ChainType::Indexing => self
|
_ if _parent_chain_type == ChainType::Indexing => self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, lhs, level)
|
.eval_expr(scope, global, state, lib, this_ptr, lhs, level)
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
super::ChainArgument::from_index_value(v.flatten(), lhs.position())
|
super::ChainArgument::from_index_value(
|
||||||
|
v.flatten(),
|
||||||
|
lhs.start_position(),
|
||||||
|
)
|
||||||
})?,
|
})?,
|
||||||
expr => unreachable!("unknown chained expression: {:?}", expr),
|
expr => unreachable!("unknown chained expression: {:?}", expr),
|
||||||
};
|
};
|
||||||
@ -825,7 +831,7 @@ impl Engine {
|
|||||||
_ if _parent_chain_type == ChainType::Indexing => idx_values.push(
|
_ if _parent_chain_type == ChainType::Indexing => idx_values.push(
|
||||||
self.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
self.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
super::ChainArgument::from_index_value(v.flatten(), expr.position())
|
super::ChainArgument::from_index_value(v.flatten(), expr.start_position())
|
||||||
})?,
|
})?,
|
||||||
),
|
),
|
||||||
_ => unreachable!("unknown chained expression: {:?}", expr),
|
_ => unreachable!("unknown chained expression: {:?}", expr),
|
||||||
@ -1044,7 +1050,7 @@ impl Engine {
|
|||||||
let pos = Position::NONE;
|
let pos = Position::NONE;
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, fn_name, hash_get, args, true, true, pos, None, level,
|
None, global, state, lib, fn_name, hash_get, args, true, true, pos, level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v.into())
|
.map(|(v, _)| v.into())
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
use super::{EvalContext, EvalState, GlobalRuntimeState};
|
use super::{EvalContext, EvalState, GlobalRuntimeState};
|
||||||
use crate::ast::{ASTNode, Expr, Stmt};
|
use crate::ast::{ASTNode, Expr, Stmt};
|
||||||
use crate::{Dynamic, Engine, Identifier, Module, Position, RhaiResultOf, Scope};
|
use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope};
|
||||||
use std::fmt;
|
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
use std::{fmt, mem};
|
||||||
|
|
||||||
/// Callback function to initialize the debugger.
|
/// Callback function to initialize the debugger.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
@ -17,11 +17,22 @@ pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
|
|||||||
|
|
||||||
/// Callback function for debugging.
|
/// Callback function for debugging.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type OnDebuggerCallback =
|
pub type OnDebuggerCallback = dyn Fn(
|
||||||
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>;
|
&mut EvalContext,
|
||||||
|
DebuggerEvent,
|
||||||
|
ASTNode,
|
||||||
|
Option<&str>,
|
||||||
|
Position,
|
||||||
|
) -> RhaiResultOf<DebuggerCommand>;
|
||||||
/// Callback function for debugging.
|
/// Callback function for debugging.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
|
pub type OnDebuggerCallback = dyn Fn(
|
||||||
|
&mut EvalContext,
|
||||||
|
DebuggerEvent,
|
||||||
|
ASTNode,
|
||||||
|
Option<&str>,
|
||||||
|
Position,
|
||||||
|
) -> RhaiResultOf<DebuggerCommand>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync;
|
+ Sync;
|
||||||
|
|
||||||
@ -36,6 +47,50 @@ pub enum DebuggerCommand {
|
|||||||
StepOver,
|
StepOver,
|
||||||
// Run to the next statement, skipping over functions.
|
// Run to the next statement, skipping over functions.
|
||||||
Next,
|
Next,
|
||||||
|
// Run to the end of the current function call.
|
||||||
|
FunctionExit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DebuggerCommand {
|
||||||
|
#[inline(always)]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The debugger status.
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
|
pub enum DebuggerStatus {
|
||||||
|
// Stop at the next statement or expression.
|
||||||
|
Next(bool, bool),
|
||||||
|
// Run to the end of the current level of function call.
|
||||||
|
FunctionExit(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DebuggerStatus {
|
||||||
|
#[inline(always)]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebuggerStatus {
|
||||||
|
pub const CONTINUE: Self = Self::Next(false, false);
|
||||||
|
pub const STEP: Self = Self::Next(true, true);
|
||||||
|
pub const NEXT: Self = Self::Next(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A event that triggers the debugger.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum DebuggerEvent<'a> {
|
||||||
|
// Break on next step.
|
||||||
|
Step,
|
||||||
|
// Break on break-point.
|
||||||
|
BreakPoint(usize),
|
||||||
|
// Return from a function with a value.
|
||||||
|
FunctionExitWithValue(&'a Dynamic),
|
||||||
|
// Return from a function with a value.
|
||||||
|
FunctionExitWithError(&'a EvalAltResult),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A break-point for debugging.
|
/// A break-point for debugging.
|
||||||
@ -158,7 +213,6 @@ impl BreakPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A function call.
|
/// A function call.
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct CallStackFrame {
|
pub struct CallStackFrame {
|
||||||
/// Function name.
|
/// Function name.
|
||||||
@ -171,7 +225,6 @@ pub struct CallStackFrame {
|
|||||||
pub pos: Position,
|
pub pos: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
impl fmt::Display for CallStackFrame {
|
impl fmt::Display for CallStackFrame {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mut fp = f.debug_tuple(&self.fn_name);
|
let mut fp = f.debug_tuple(&self.fn_name);
|
||||||
@ -198,13 +251,12 @@ impl fmt::Display for CallStackFrame {
|
|||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct Debugger {
|
pub struct Debugger {
|
||||||
/// The current status command.
|
/// The current status command.
|
||||||
status: DebuggerCommand,
|
pub(crate) status: DebuggerStatus,
|
||||||
/// The current state.
|
/// The current state.
|
||||||
state: Dynamic,
|
state: Dynamic,
|
||||||
/// The current set of break-points.
|
/// The current set of break-points.
|
||||||
break_points: Vec<BreakPoint>,
|
break_points: Vec<BreakPoint>,
|
||||||
/// The current function call stack.
|
/// The current function call stack.
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
call_stack: Vec<CallStackFrame>,
|
call_stack: Vec<CallStackFrame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,9 +267,9 @@ impl Debugger {
|
|||||||
pub fn new(engine: &Engine) -> Self {
|
pub fn new(engine: &Engine) -> Self {
|
||||||
Self {
|
Self {
|
||||||
status: if engine.debugger.is_some() {
|
status: if engine.debugger.is_some() {
|
||||||
DebuggerCommand::StepInto
|
DebuggerStatus::STEP
|
||||||
} else {
|
} else {
|
||||||
DebuggerCommand::Continue
|
DebuggerStatus::CONTINUE
|
||||||
},
|
},
|
||||||
state: if let Some((ref init, _)) = engine.debugger {
|
state: if let Some((ref init, _)) = engine.debugger {
|
||||||
init()
|
init()
|
||||||
@ -225,7 +277,6 @@ impl Debugger {
|
|||||||
Dynamic::UNIT
|
Dynamic::UNIT
|
||||||
},
|
},
|
||||||
break_points: Vec::new(),
|
break_points: Vec::new(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
call_stack: Vec::new(),
|
call_stack: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,26 +293,17 @@ impl Debugger {
|
|||||||
&mut self.state
|
&mut self.state
|
||||||
}
|
}
|
||||||
/// Get the current call stack.
|
/// Get the current call stack.
|
||||||
///
|
|
||||||
/// Not available under `no_function`.
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn call_stack(&self) -> &[CallStackFrame] {
|
pub fn call_stack(&self) -> &[CallStackFrame] {
|
||||||
&self.call_stack
|
&self.call_stack
|
||||||
}
|
}
|
||||||
/// Rewind the function call stack to a particular depth.
|
/// Rewind the function call stack to a particular depth.
|
||||||
///
|
|
||||||
/// Not available under `no_function`.
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn rewind_call_stack(&mut self, len: usize) {
|
pub(crate) fn rewind_call_stack(&mut self, len: usize) {
|
||||||
self.call_stack.truncate(len);
|
self.call_stack.truncate(len);
|
||||||
}
|
}
|
||||||
/// Add a new frame to the function call stack.
|
/// Add a new frame to the function call stack.
|
||||||
///
|
|
||||||
/// Not available under `no_function`.
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn push_call_stack_frame(
|
pub(crate) fn push_call_stack_frame(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -277,34 +319,37 @@ impl Debugger {
|
|||||||
pos,
|
pos,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/// Get the current status of this [`Debugger`].
|
/// Change the current status to [`CONTINUE`][DebuggerStatus::CONTINUE] and return the previous status.
|
||||||
#[inline(always)]
|
pub(crate) fn clear_status_if(
|
||||||
#[must_use]
|
&mut self,
|
||||||
pub fn status(&self) -> DebuggerCommand {
|
filter: impl Fn(&DebuggerStatus) -> bool,
|
||||||
self.status
|
) -> Option<DebuggerStatus> {
|
||||||
}
|
if filter(&self.status) {
|
||||||
/// Get a mutable reference to the current status of this [`Debugger`].
|
Some(mem::replace(&mut self.status, DebuggerStatus::CONTINUE))
|
||||||
#[inline(always)]
|
} else {
|
||||||
#[must_use]
|
None
|
||||||
pub fn status_mut(&mut self) -> &mut DebuggerCommand {
|
|
||||||
&mut self.status
|
|
||||||
}
|
|
||||||
/// Set the status of this [`Debugger`].
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn reset_status(&mut self, status: Option<DebuggerCommand>) {
|
|
||||||
if let Some(cmd) = status {
|
|
||||||
self.status = cmd;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Does a particular [`AST` Node][ASTNode] trigger a break-point?
|
/// Override the status of this [`Debugger`] if it is [`Some`] the current status is
|
||||||
|
/// [`CONTINUE`][DebuggerStatus::CONTINUE].
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn reset_status(&mut self, status: Option<DebuggerStatus>) {
|
||||||
|
if self.status == DebuggerStatus::CONTINUE {
|
||||||
|
if let Some(cmd) = status {
|
||||||
|
self.status = cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Returns the first break-point triggered by a particular [`AST` Node][ASTNode].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool {
|
pub fn is_break_point(&self, src: &str, node: ASTNode) -> Option<usize> {
|
||||||
let _src = src;
|
let _src = src;
|
||||||
|
|
||||||
self.break_points()
|
self.break_points()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&bp| bp.is_enabled())
|
.enumerate()
|
||||||
.any(|bp| match bp {
|
.filter(|&(_, bp)| bp.is_enabled())
|
||||||
|
.find(|&(_, bp)| match bp {
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
|
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
@ -316,13 +361,15 @@ impl Debugger {
|
|||||||
node.position() == *pos && _src == source
|
node.position() == *pos && _src == source
|
||||||
}
|
}
|
||||||
BreakPoint::AtFunctionName { name, .. } => match node {
|
BreakPoint::AtFunctionName { name, .. } => match node {
|
||||||
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
|
ASTNode::Expr(Expr::FnCall(x, _))
|
||||||
x.name == *name
|
| ASTNode::Stmt(Stmt::FnCall(x, _))
|
||||||
}
|
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, _))) => x.name == *name,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
BreakPoint::AtFunctionCall { name, args, .. } => match node {
|
BreakPoint::AtFunctionCall { name, args, .. } => match node {
|
||||||
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
|
ASTNode::Expr(Expr::FnCall(x, _))
|
||||||
|
| ASTNode::Stmt(Stmt::FnCall(x, _))
|
||||||
|
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, _))) => {
|
||||||
x.args.len() == *args && x.name == *name
|
x.args.len() == *args && x.name == *name
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
@ -333,6 +380,7 @@ impl Debugger {
|
|||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.map(|(i, _)| i)
|
||||||
}
|
}
|
||||||
/// Get a slice of all [`BreakPoint`]'s.
|
/// Get a slice of all [`BreakPoint`]'s.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -349,7 +397,7 @@ impl Debugger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Run the debugger callback.
|
/// Run the debugger callback if there is a debugging interface registered.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn run_debugger<'a>(
|
pub(crate) fn run_debugger<'a>(
|
||||||
&self,
|
&self,
|
||||||
@ -361,26 +409,23 @@ impl Engine {
|
|||||||
node: impl Into<ASTNode<'a>>,
|
node: impl Into<ASTNode<'a>>,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResultOf<()> {
|
) -> RhaiResultOf<()> {
|
||||||
if let Some(cmd) =
|
if self.debugger.is_some() {
|
||||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)?
|
if let Some(cmd) =
|
||||||
{
|
self.run_debugger_with_reset_raw(scope, global, state, lib, this_ptr, node, level)?
|
||||||
global.debugger.status = cmd;
|
{
|
||||||
|
global.debugger.status = cmd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Run the debugger callback.
|
/// Run the debugger callback if there is a debugging interface registered.
|
||||||
///
|
///
|
||||||
/// Returns `true` if the debugger needs to be reactivated at the end of the block, statement or
|
/// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
|
||||||
/// function call.
|
/// function call.
|
||||||
///
|
///
|
||||||
/// # Note
|
|
||||||
///
|
|
||||||
/// When the debugger callback return [`DebuggerCommand::StepOver`], the debugger if temporarily
|
|
||||||
/// disabled and `true` is returned.
|
|
||||||
///
|
|
||||||
/// It is up to the [`Engine`] to reactivate the debugger.
|
/// It is up to the [`Engine`] to reactivate the debugger.
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn run_debugger_with_reset<'a>(
|
pub(crate) fn run_debugger_with_reset<'a>(
|
||||||
&self,
|
&self,
|
||||||
@ -391,61 +436,125 @@ impl Engine {
|
|||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
node: impl Into<ASTNode<'a>>,
|
node: impl Into<ASTNode<'a>>,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResultOf<Option<DebuggerCommand>> {
|
) -> RhaiResultOf<Option<DebuggerStatus>> {
|
||||||
if let Some((_, ref on_debugger)) = self.debugger {
|
if self.debugger.is_some() {
|
||||||
let node = node.into();
|
self.run_debugger_with_reset_raw(scope, global, state, lib, this_ptr, node, level)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Run the debugger callback.
|
||||||
|
///
|
||||||
|
/// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
|
||||||
|
/// function call.
|
||||||
|
///
|
||||||
|
/// It is up to the [`Engine`] to reactivate the debugger.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn run_debugger_with_reset_raw<'a>(
|
||||||
|
&self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
global: &mut GlobalRuntimeState,
|
||||||
|
state: &mut EvalState,
|
||||||
|
lib: &[&Module],
|
||||||
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
|
node: impl Into<ASTNode<'a>>,
|
||||||
|
level: usize,
|
||||||
|
) -> RhaiResultOf<Option<DebuggerStatus>> {
|
||||||
|
let node = node.into();
|
||||||
|
|
||||||
// Skip transitive nodes
|
// Skip transitive nodes
|
||||||
match node {
|
match node {
|
||||||
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
|
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let stop = match global.debugger.status {
|
let stop = match global.debugger.status {
|
||||||
DebuggerCommand::Continue => false,
|
DebuggerStatus::Next(false, false) => false,
|
||||||
DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)),
|
DebuggerStatus::Next(true, false) => matches!(node, ASTNode::Stmt(_)),
|
||||||
DebuggerCommand::StepInto | DebuggerCommand::StepOver => true,
|
DebuggerStatus::Next(false, true) => matches!(node, ASTNode::Expr(_)),
|
||||||
};
|
DebuggerStatus::Next(true, true) => true,
|
||||||
|
DebuggerStatus::FunctionExit(_) => false,
|
||||||
|
};
|
||||||
|
|
||||||
if !stop && !global.debugger.is_break_point(&global.source, node) {
|
let event = if stop {
|
||||||
|
DebuggerEvent::Step
|
||||||
|
} else {
|
||||||
|
if let Some(bp) = global.debugger.is_break_point(&global.source, node) {
|
||||||
|
DebuggerEvent::BreakPoint(bp)
|
||||||
|
} else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let source = global.source.clone();
|
self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
|
||||||
let source = if source.is_empty() {
|
}
|
||||||
None
|
/// Run the debugger callback unconditionally.
|
||||||
} else {
|
///
|
||||||
Some(source.as_str())
|
/// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
|
||||||
};
|
/// function call.
|
||||||
|
///
|
||||||
|
/// It is up to the [`Engine`] to reactivate the debugger.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn run_debugger_raw<'a>(
|
||||||
|
&self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
global: &mut GlobalRuntimeState,
|
||||||
|
state: &mut EvalState,
|
||||||
|
lib: &[&Module],
|
||||||
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
|
node: ASTNode<'a>,
|
||||||
|
event: DebuggerEvent,
|
||||||
|
level: usize,
|
||||||
|
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
|
||||||
|
let source = global.source.clone();
|
||||||
|
let source = if source.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(source.as_str())
|
||||||
|
};
|
||||||
|
|
||||||
let mut context = crate::EvalContext {
|
let mut context = crate::EvalContext {
|
||||||
engine: self,
|
engine: self,
|
||||||
scope,
|
scope,
|
||||||
global,
|
global,
|
||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
this_ptr,
|
this_ptr,
|
||||||
level,
|
level,
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = on_debugger(&mut context, node, source, node.position())?;
|
if let Some((_, ref on_debugger)) = self.debugger {
|
||||||
|
let command = on_debugger(&mut context, event, node, source, node.position())?;
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
DebuggerCommand::Continue => {
|
DebuggerCommand::Continue => {
|
||||||
global.debugger.status = DebuggerCommand::Continue;
|
global.debugger.status = DebuggerStatus::CONTINUE;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
DebuggerCommand::Next => {
|
DebuggerCommand::Next => {
|
||||||
global.debugger.status = DebuggerCommand::Continue;
|
global.debugger.status = DebuggerStatus::CONTINUE;
|
||||||
Ok(Some(DebuggerCommand::Next))
|
Ok(Some(DebuggerStatus::NEXT))
|
||||||
}
|
|
||||||
DebuggerCommand::StepInto => {
|
|
||||||
global.debugger.status = DebuggerCommand::StepInto;
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
DebuggerCommand::StepOver => {
|
DebuggerCommand::StepOver => {
|
||||||
global.debugger.status = DebuggerCommand::Continue;
|
global.debugger.status = DebuggerStatus::CONTINUE;
|
||||||
Ok(Some(DebuggerCommand::StepOver))
|
Ok(Some(DebuggerStatus::STEP))
|
||||||
|
}
|
||||||
|
DebuggerCommand::StepInto => {
|
||||||
|
global.debugger.status = DebuggerStatus::STEP;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
DebuggerCommand::FunctionExit => {
|
||||||
|
// Bump a level if it is a function call
|
||||||
|
let level = match node {
|
||||||
|
ASTNode::Expr(Expr::FnCall(_, _))
|
||||||
|
| ASTNode::Stmt(Stmt::FnCall(_, _))
|
||||||
|
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(_, _))) => context.call_level() + 1,
|
||||||
|
_ => context.call_level(),
|
||||||
|
};
|
||||||
|
global.debugger.status = DebuggerStatus::FunctionExit(level);
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -50,17 +50,22 @@ impl Engine {
|
|||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
|
level: usize,
|
||||||
) -> RhaiResultOf<(Target<'s>, Position)> {
|
) -> RhaiResultOf<(Target<'s>, Position)> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Variable(Some(_), _, _) => {
|
Expr::Variable(Some(_), _, _) => {
|
||||||
self.search_scope_only(scope, global, state, lib, this_ptr, expr)
|
self.search_scope_only(scope, global, state, lib, this_ptr, expr, level)
|
||||||
}
|
}
|
||||||
Expr::Variable(None, _var_pos, v) => match v.as_ref() {
|
Expr::Variable(None, _var_pos, v) => match v.as_ref() {
|
||||||
// Normal variable access
|
// Normal variable access
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
(_, None, _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr),
|
(_, None, _) => {
|
||||||
|
self.search_scope_only(scope, global, state, lib, this_ptr, expr, level)
|
||||||
|
}
|
||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
(_, (), _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr),
|
(_, (), _) => {
|
||||||
|
self.search_scope_only(scope, global, state, lib, this_ptr, expr, level)
|
||||||
|
}
|
||||||
|
|
||||||
// Qualified variable access
|
// Qualified variable access
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -136,6 +141,7 @@ impl Engine {
|
|||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
|
level: usize,
|
||||||
) -> RhaiResultOf<(Target<'s>, Position)> {
|
) -> RhaiResultOf<(Target<'s>, Position)> {
|
||||||
// Make sure that the pointer indirection is taken only when absolutely necessary.
|
// Make sure that the pointer indirection is taken only when absolutely necessary.
|
||||||
|
|
||||||
@ -148,7 +154,7 @@ impl Engine {
|
|||||||
Err(ERR::ErrorUnboundThis(*pos).into())
|
Err(ERR::ErrorUnboundThis(*pos).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if state.always_search_scope => (0, expr.position()),
|
_ if state.always_search_scope => (0, expr.start_position()),
|
||||||
Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos),
|
Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos),
|
||||||
Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos),
|
Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos),
|
||||||
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
|
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
|
||||||
@ -163,7 +169,7 @@ impl Engine {
|
|||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
this_ptr,
|
this_ptr,
|
||||||
level: 0,
|
level,
|
||||||
};
|
};
|
||||||
match resolve_var(
|
match resolve_var(
|
||||||
expr.get_variable_name(true).expect("`Expr::Variable`"),
|
expr.get_variable_name(true).expect("`Expr::Variable`"),
|
||||||
@ -236,8 +242,8 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.make_function_call(
|
self.make_function_call(
|
||||||
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes, pos,
|
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes,
|
||||||
*capture, level,
|
*capture, pos, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +303,7 @@ impl Engine {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
|
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
|
||||||
} else {
|
} else {
|
||||||
self.search_namespace(scope, global, state, lib, this_ptr, expr)
|
self.search_namespace(scope, global, state, lib, this_ptr, expr, level)
|
||||||
.map(|(val, _)| val.take_or_clone())
|
.map(|(val, _)| val.take_or_clone())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -345,12 +351,13 @@ impl Engine {
|
|||||||
&mut (&mut concat).into(),
|
&mut (&mut concat).into(),
|
||||||
("", Position::NONE),
|
("", Position::NONE),
|
||||||
item,
|
item,
|
||||||
|
level,
|
||||||
) {
|
) {
|
||||||
result = Err(err.fill_position(expr.position()));
|
result = Err(err.fill_position(expr.start_position()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = expr.position();
|
pos = expr.start_position();
|
||||||
}
|
}
|
||||||
|
|
||||||
result.map(|_| concat)
|
result.map(|_| concat)
|
||||||
@ -503,7 +510,7 @@ impl Engine {
|
|||||||
|
|
||||||
let result = (custom_def.func)(&mut context, &expressions);
|
let result = (custom_def.func)(&mut context, &expressions);
|
||||||
|
|
||||||
self.check_return_value(result, expr.position())
|
self.check_return_value(result, expr.start_position())
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
|
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
|
||||||
|
@ -5,6 +5,12 @@ use crate::{Engine, Identifier};
|
|||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{fmt, marker::PhantomData};
|
use std::{fmt, marker::PhantomData};
|
||||||
|
|
||||||
|
/// Collection of globally-defined constants.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub type GlobalConstants =
|
||||||
|
crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>;
|
||||||
|
|
||||||
/// _(internals)_ Global runtime states.
|
/// _(internals)_ Global runtime states.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
//
|
//
|
||||||
@ -44,9 +50,7 @@ pub struct GlobalRuntimeState<'a> {
|
|||||||
/// Interior mutability is needed because it is shared in order to aid in cloning.
|
/// Interior mutability is needed because it is shared in order to aid in cloning.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub(crate) constants: Option<
|
pub(crate) constants: Option<GlobalConstants>,
|
||||||
crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>,
|
|
||||||
>,
|
|
||||||
/// Debugging interface.
|
/// Debugging interface.
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
pub debugger: super::Debugger,
|
pub debugger: super::Debugger,
|
||||||
|
@ -11,11 +11,14 @@ mod target;
|
|||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
pub use chaining::{ChainArgument, ChainType};
|
pub use chaining::{ChainArgument, ChainType};
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
#[cfg(not(feature = "no_function"))]
|
pub use debugger::{
|
||||||
pub use debugger::CallStackFrame;
|
BreakPoint, CallStackFrame, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus,
|
||||||
#[cfg(feature = "debugging")]
|
OnDebuggerCallback, OnDebuggingInit,
|
||||||
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback, OnDebuggingInit};
|
};
|
||||||
pub use eval_context::EvalContext;
|
pub use eval_context::EvalContext;
|
||||||
pub use eval_state::EvalState;
|
pub use eval_state::EvalState;
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub use global_state::GlobalConstants;
|
||||||
pub use global_state::GlobalRuntimeState;
|
pub use global_state::GlobalRuntimeState;
|
||||||
pub use target::{calc_index, calc_offset_len, Target};
|
pub use target::{calc_index, calc_offset_len, Target};
|
||||||
|
136
src/eval/stmt.rs
136
src/eval/stmt.rs
@ -1,6 +1,6 @@
|
|||||||
//! Module defining functions for evaluating a statement.
|
//! Module defining functions for evaluating a statement.
|
||||||
|
|
||||||
use super::{EvalState, GlobalRuntimeState, Target};
|
use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||||
};
|
};
|
||||||
@ -120,6 +120,7 @@ impl Engine {
|
|||||||
target: &mut Target,
|
target: &mut Target,
|
||||||
root: (&str, Position),
|
root: (&str, Position),
|
||||||
new_val: Dynamic,
|
new_val: Dynamic,
|
||||||
|
level: usize,
|
||||||
) -> RhaiResultOf<()> {
|
) -> RhaiResultOf<()> {
|
||||||
if target.is_read_only() {
|
if target.is_read_only() {
|
||||||
// Assignment to constant variable
|
// Assignment to constant variable
|
||||||
@ -152,9 +153,10 @@ impl Engine {
|
|||||||
|
|
||||||
let hash = hash_op_assign;
|
let hash = hash_op_assign;
|
||||||
let args = &mut [lhs_ptr_inner, &mut new_val];
|
let args = &mut [lhs_ptr_inner, &mut new_val];
|
||||||
|
let level = level + 1;
|
||||||
|
|
||||||
match self.call_native_fn(
|
match self.call_native_fn(
|
||||||
global, state, lib, op_assign, hash, args, true, true, op_pos,
|
global, state, lib, op_assign, hash, args, true, true, op_pos, level,
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -164,7 +166,7 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
// Expand to `var = var op rhs`
|
// Expand to `var = var op rhs`
|
||||||
let (value, _) = self.call_native_fn(
|
let (value, _) = self.call_native_fn(
|
||||||
global, state, lib, op, hash_op, args, true, false, op_pos,
|
global, state, lib, op, hash_op, args, true, false, op_pos, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -238,7 +240,7 @@ impl Engine {
|
|||||||
|
|
||||||
if let Ok(rhs_val) = rhs_result {
|
if let Ok(rhs_val) = rhs_result {
|
||||||
let search_result =
|
let search_result =
|
||||||
self.search_namespace(scope, global, state, lib, this_ptr, lhs);
|
self.search_namespace(scope, global, state, lib, this_ptr, lhs, level);
|
||||||
|
|
||||||
if let Ok(search_val) = search_result {
|
if let Ok(search_val) = search_result {
|
||||||
let (mut lhs_ptr, pos) = search_val;
|
let (mut lhs_ptr, pos) = search_val;
|
||||||
@ -263,8 +265,9 @@ impl Engine {
|
|||||||
&mut lhs_ptr,
|
&mut lhs_ptr,
|
||||||
(var_name, pos),
|
(var_name, pos),
|
||||||
rhs_val,
|
rhs_val,
|
||||||
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(rhs.position()))
|
.map_err(|err| err.fill_position(rhs.start_position()))
|
||||||
.map(|_| Dynamic::UNIT)
|
.map(|_| Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
search_result.map(|_| Dynamic::UNIT)
|
search_result.map(|_| Dynamic::UNIT)
|
||||||
@ -280,7 +283,7 @@ impl Engine {
|
|||||||
.map(Dynamic::flatten);
|
.map(Dynamic::flatten);
|
||||||
|
|
||||||
if let Ok(rhs_val) = rhs_result {
|
if let Ok(rhs_val) = rhs_result {
|
||||||
let _new_val = Some(((rhs_val, rhs.position()), (*op_info, *op_pos)));
|
let _new_val = Some(((rhs_val, rhs.start_position()), (*op_info, *op_pos)));
|
||||||
|
|
||||||
// Must be either `var[index] op= val` or `var.prop op= val`
|
// Must be either `var[index] op= val` or `var.prop op= val`
|
||||||
match lhs {
|
match lhs {
|
||||||
@ -683,7 +686,7 @@ impl Engine {
|
|||||||
|
|
||||||
loop_result
|
loop_result
|
||||||
} else {
|
} else {
|
||||||
Err(ERR::ErrorFor(expr.position()).into())
|
Err(ERR::ErrorFor(expr.start_position()).into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
iter_result
|
iter_result
|
||||||
@ -799,9 +802,14 @@ impl Engine {
|
|||||||
// Empty return
|
// Empty return
|
||||||
Stmt::Return(_, None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
Stmt::Return(_, None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
||||||
|
|
||||||
|
// Let/const statement - shadowing disallowed
|
||||||
|
Stmt::Var(_, x, _, pos) if !self.allow_shadowing() && scope.contains(&x.name) => {
|
||||||
|
Err(ERR::ErrorVariableExists(x.name.to_string(), *pos).into())
|
||||||
|
}
|
||||||
// Let/const statement
|
// Let/const statement
|
||||||
Stmt::Var(expr, x, options, _) => {
|
Stmt::Var(expr, x, options, pos) => {
|
||||||
let var_name = &x.name;
|
let var_name = &x.name;
|
||||||
|
|
||||||
let entry_type = if options.contains(AST_OPTION_CONSTANT) {
|
let entry_type = if options.contains(AST_OPTION_CONSTANT) {
|
||||||
AccessMode::ReadOnly
|
AccessMode::ReadOnly
|
||||||
} else {
|
} else {
|
||||||
@ -809,48 +817,79 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
let export = options.contains(AST_OPTION_EXPORTED);
|
let export = options.contains(AST_OPTION_EXPORTED);
|
||||||
|
|
||||||
let value_result = self
|
let result = if let Some(ref filter) = self.def_var_filter {
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
let shadowing = scope.contains(var_name);
|
||||||
.map(Dynamic::flatten);
|
let scope_level = state.scope_level;
|
||||||
|
let is_const = entry_type == AccessMode::ReadOnly;
|
||||||
if let Ok(value) = value_result {
|
let context = EvalContext {
|
||||||
let _alias = if !rewind_scope {
|
engine: self,
|
||||||
#[cfg(not(feature = "no_function"))]
|
scope,
|
||||||
#[cfg(not(feature = "no_module"))]
|
global,
|
||||||
if state.scope_level == 0
|
state,
|
||||||
&& entry_type == AccessMode::ReadOnly
|
lib,
|
||||||
&& lib.iter().any(|&m| !m.is_empty())
|
this_ptr,
|
||||||
{
|
level: level,
|
||||||
if global.constants.is_none() {
|
|
||||||
global.constants = Some(crate::Shared::new(crate::Locked::new(
|
|
||||||
std::collections::BTreeMap::new(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
crate::func::locked_write(global.constants.as_ref().unwrap())
|
|
||||||
.insert(var_name.clone(), value.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if export {
|
|
||||||
Some(var_name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else if export {
|
|
||||||
unreachable!("exported variable not on global level");
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.push_dynamic_value(var_name.clone(), entry_type, value);
|
match filter(var_name, is_const, scope_level, shadowing, &context) {
|
||||||
|
Ok(true) => None,
|
||||||
#[cfg(not(feature = "no_module"))]
|
Ok(false) => Some(Err(ERR::ErrorRuntime(
|
||||||
if let Some(alias) = _alias {
|
format!("Variable cannot be defined: {}", var_name).into(),
|
||||||
scope.add_entry_alias(scope.len() - 1, alias.clone());
|
*pos,
|
||||||
|
)
|
||||||
|
.into())),
|
||||||
|
err @ Err(_) => Some(err),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Dynamic::UNIT)
|
|
||||||
} else {
|
} else {
|
||||||
value_result
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(result) = result {
|
||||||
|
result.map(|_| Dynamic::UNIT)
|
||||||
|
} else {
|
||||||
|
let value_result = self
|
||||||
|
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||||
|
.map(Dynamic::flatten);
|
||||||
|
|
||||||
|
if let Ok(value) = value_result {
|
||||||
|
let _alias = if !rewind_scope {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
if state.scope_level == 0
|
||||||
|
&& entry_type == AccessMode::ReadOnly
|
||||||
|
&& lib.iter().any(|&m| !m.is_empty())
|
||||||
|
{
|
||||||
|
if global.constants.is_none() {
|
||||||
|
global.constants = Some(crate::Shared::new(
|
||||||
|
crate::Locked::new(std::collections::BTreeMap::new()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
crate::func::locked_write(global.constants.as_ref().unwrap())
|
||||||
|
.insert(var_name.clone(), value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if export {
|
||||||
|
Some(var_name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else if export {
|
||||||
|
unreachable!("exported variable not on global level");
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.push_dynamic_value(var_name.clone(), entry_type, value);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
if let Some(alias) = _alias {
|
||||||
|
scope.add_entry_alias(scope.len() - 1, alias.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
} else {
|
||||||
|
value_result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -866,9 +905,10 @@ impl Engine {
|
|||||||
let path_result = self
|
let path_result = self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, &expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, &expr, level)
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
|
let typ = v.type_name();
|
||||||
v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
|
v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
|
||||||
self.make_type_mismatch_err::<crate::ImmutableString>(
|
self.make_type_mismatch_err::<crate::ImmutableString>(
|
||||||
"",
|
typ,
|
||||||
expr.position(),
|
expr.position(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -877,7 +917,7 @@ impl Engine {
|
|||||||
if let Ok(path) = path_result {
|
if let Ok(path) = path_result {
|
||||||
use crate::ModuleResolver;
|
use crate::ModuleResolver;
|
||||||
|
|
||||||
let path_pos = expr.position();
|
let path_pos = expr.start_position();
|
||||||
|
|
||||||
let resolver = global.embedded_module_resolver.clone();
|
let resolver = global.embedded_module_resolver.clone();
|
||||||
|
|
||||||
|
219
src/func/call.rs
219
src/func/call.rs
@ -328,6 +328,8 @@ impl Engine {
|
|||||||
result.as_ref().map(Box::as_ref)
|
result.as_ref().map(Box::as_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Main Entry-Point
|
||||||
|
///
|
||||||
/// Call a native Rust function registered with the [`Engine`].
|
/// Call a native Rust function registered with the [`Engine`].
|
||||||
///
|
///
|
||||||
/// # WARNING
|
/// # WARNING
|
||||||
@ -347,6 +349,7 @@ impl Engine {
|
|||||||
is_ref_mut: bool,
|
is_ref_mut: bool,
|
||||||
is_op_assign: bool,
|
is_op_assign: bool,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
level: usize,
|
||||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, pos)?;
|
self.inc_operations(&mut global.num_operations, pos)?;
|
||||||
@ -365,43 +368,92 @@ impl Engine {
|
|||||||
is_op_assign,
|
is_op_assign,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(FnResolutionCacheEntry { func, source }) = func {
|
if func.is_some() {
|
||||||
assert!(func.is_native());
|
let is_method = func.map(|f| f.func.is_method()).unwrap_or(false);
|
||||||
|
|
||||||
// Calling pure function but the first argument is a reference?
|
// Push a new call stack frame
|
||||||
let mut backup: Option<ArgBackup> = None;
|
#[cfg(feature = "debugging")]
|
||||||
if is_ref_mut && func.is_pure() && !args.is_empty() {
|
let orig_call_stack_len = global.debugger.call_stack().len();
|
||||||
// Clone the first argument
|
|
||||||
backup = Some(ArgBackup::new());
|
|
||||||
backup
|
|
||||||
.as_mut()
|
|
||||||
.expect("`Some`")
|
|
||||||
.change_first_arg_to_copy(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run external function
|
let mut _result = if let Some(FnResolutionCacheEntry { func, source }) = func {
|
||||||
let source = match (source.as_str(), parent_source.as_str()) {
|
assert!(func.is_native());
|
||||||
("", "") => None,
|
|
||||||
("", s) | (s, _) => Some(s),
|
|
||||||
};
|
|
||||||
|
|
||||||
let context = (self, name, source, &*global, lib, pos).into();
|
// Calling pure function but the first argument is a reference?
|
||||||
|
let mut backup: Option<ArgBackup> = None;
|
||||||
|
if is_ref_mut && func.is_pure() && !args.is_empty() {
|
||||||
|
// Clone the first argument
|
||||||
|
backup = Some(ArgBackup::new());
|
||||||
|
backup
|
||||||
|
.as_mut()
|
||||||
|
.expect("`Some`")
|
||||||
|
.change_first_arg_to_copy(args);
|
||||||
|
}
|
||||||
|
|
||||||
let result = if func.is_plugin_fn() {
|
let source = match (source.as_str(), parent_source.as_str()) {
|
||||||
func.get_plugin_fn()
|
("", "") => None,
|
||||||
.expect("plugin function")
|
("", s) | (s, _) => Some(s),
|
||||||
.call(context, args)
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
|
global.debugger.push_call_stack_frame(
|
||||||
|
name,
|
||||||
|
args.iter().map(|v| (*v).clone()).collect(),
|
||||||
|
source.unwrap_or(""),
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run external function
|
||||||
|
let context = (self, name, source, &*global, lib, pos, level).into();
|
||||||
|
|
||||||
|
let result = if func.is_plugin_fn() {
|
||||||
|
func.get_plugin_fn()
|
||||||
|
.expect("plugin function")
|
||||||
|
.call(context, args)
|
||||||
|
} else {
|
||||||
|
func.get_native_fn().expect("native function")(context, args)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Restore the original reference
|
||||||
|
if let Some(bk) = backup {
|
||||||
|
bk.restore_first_arg(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
} else {
|
} else {
|
||||||
func.get_native_fn().expect("native function")(context, args)
|
unreachable!("`Some`");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Restore the original reference
|
#[cfg(feature = "debugging")]
|
||||||
if let Some(bk) = backup {
|
{
|
||||||
bk.restore_first_arg(args)
|
let trigger = match global.debugger.status {
|
||||||
|
crate::eval::DebuggerStatus::FunctionExit(n) => n >= level,
|
||||||
|
crate::eval::DebuggerStatus::Next(_, true) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if trigger {
|
||||||
|
let scope = &mut &mut Scope::new();
|
||||||
|
let node = crate::ast::Stmt::Noop(pos);
|
||||||
|
let node = (&node).into();
|
||||||
|
let event = match _result {
|
||||||
|
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
|
||||||
|
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
|
||||||
|
};
|
||||||
|
match self
|
||||||
|
.run_debugger_raw(scope, global, state, lib, &mut None, node, event, level)
|
||||||
|
{
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => _result = Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the call stack
|
||||||
|
global.debugger.rewind_call_stack(orig_call_stack_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the return value (including data sizes)
|
// Check the return value (including data sizes)
|
||||||
let result = self.check_return_value(result, pos)?;
|
let result = self.check_return_value(_result, pos)?;
|
||||||
|
|
||||||
// Check the data size of any `&mut` object, which may be changed.
|
// Check the data size of any `&mut` object, which may be changed.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -443,7 +495,7 @@ impl Engine {
|
|||||||
(Dynamic::UNIT, false)
|
(Dynamic::UNIT, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (result, func.is_method()),
|
_ => (result, is_method),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,6 +582,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Main Entry-Point
|
||||||
|
///
|
||||||
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
||||||
///
|
///
|
||||||
/// # WARNING
|
/// # WARNING
|
||||||
@ -540,6 +594,7 @@ impl Engine {
|
|||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
pub(crate) fn exec_fn_call(
|
pub(crate) fn exec_fn_call(
|
||||||
&self,
|
&self,
|
||||||
|
scope: Option<&mut Scope>,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
state: &mut EvalState,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
@ -549,7 +604,6 @@ impl Engine {
|
|||||||
is_ref_mut: bool,
|
is_ref_mut: bool,
|
||||||
is_method_call: bool,
|
is_method_call: bool,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
scope: Option<&mut Scope>,
|
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> {
|
fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
@ -562,7 +616,6 @@ impl Engine {
|
|||||||
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
||||||
|
|
||||||
let _scope = scope;
|
let _scope = scope;
|
||||||
let _level = level;
|
|
||||||
let _is_method_call = is_method_call;
|
let _is_method_call = is_method_call;
|
||||||
|
|
||||||
// These may be redirected from method style calls.
|
// These may be redirected from method style calls.
|
||||||
@ -612,6 +665,8 @@ impl Engine {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let level = level + 1;
|
||||||
|
|
||||||
// Script-defined function call?
|
// Script-defined function call?
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if let Some(FnResolutionCacheEntry { func, mut source }) = self
|
if let Some(FnResolutionCacheEntry { func, mut source }) = self
|
||||||
@ -651,8 +706,6 @@ impl Engine {
|
|||||||
// Method call of script function - map first argument to `this`
|
// Method call of script function - map first argument to `this`
|
||||||
let (first_arg, rest_args) = args.split_first_mut().unwrap();
|
let (first_arg, rest_args) = args.split_first_mut().unwrap();
|
||||||
|
|
||||||
let level = _level + 1;
|
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
scope,
|
scope,
|
||||||
global,
|
global,
|
||||||
@ -661,8 +714,8 @@ impl Engine {
|
|||||||
&mut Some(*first_arg),
|
&mut Some(*first_arg),
|
||||||
func,
|
func,
|
||||||
rest_args,
|
rest_args,
|
||||||
pos,
|
|
||||||
true,
|
true,
|
||||||
|
pos,
|
||||||
level,
|
level,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -679,10 +732,8 @@ impl Engine {
|
|||||||
.change_first_arg_to_copy(args);
|
.change_first_arg_to_copy(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
let level = _level + 1;
|
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
scope, global, state, lib, &mut None, func, args, pos, true, level,
|
scope, global, state, lib, &mut None, func, args, true, pos, level,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Restore the original reference
|
// Restore the original reference
|
||||||
@ -702,7 +753,7 @@ impl Engine {
|
|||||||
// Native function call
|
// Native function call
|
||||||
let hash = hashes.native;
|
let hash = hashes.native;
|
||||||
self.call_native_fn(
|
self.call_native_fn(
|
||||||
global, state, lib, fn_name, hash, args, is_ref_mut, false, pos,
|
global, state, lib, fn_name, hash, args, is_ref_mut, false, pos, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -764,7 +815,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, fn_name, new_hash, &mut args, false, false, pos, None,
|
None, global, state, lib, fn_name, new_hash, &mut args, false, false, pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -804,7 +855,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos, None,
|
None, global, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -876,7 +927,7 @@ impl Engine {
|
|||||||
args.extend(call_args.iter_mut());
|
args.extend(call_args.iter_mut());
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos, None,
|
None, global, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -901,9 +952,9 @@ impl Engine {
|
|||||||
state: &mut EvalState,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
level: usize,
|
|
||||||
arg_expr: &Expr,
|
arg_expr: &Expr,
|
||||||
constants: &[Dynamic],
|
constants: &[Dynamic],
|
||||||
|
level: usize,
|
||||||
) -> RhaiResultOf<(Dynamic, Position)> {
|
) -> RhaiResultOf<(Dynamic, Position)> {
|
||||||
Ok((
|
Ok((
|
||||||
if let Expr::Stack(slot, _) = arg_expr {
|
if let Expr::Stack(slot, _) = arg_expr {
|
||||||
@ -915,9 +966,21 @@ impl Engine {
|
|||||||
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)?
|
// Do not match function exit for arguments
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
let reset_debugger = global.debugger.clear_status_if(|status| {
|
||||||
|
matches!(status, crate::eval::DebuggerStatus::FunctionExit(_))
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level);
|
||||||
|
|
||||||
|
// Restore function exit status
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
global.debugger.reset_status(reset_debugger);
|
||||||
|
|
||||||
|
result?
|
||||||
},
|
},
|
||||||
arg_expr.position(),
|
arg_expr.start_position(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -934,8 +997,8 @@ impl Engine {
|
|||||||
args_expr: &[Expr],
|
args_expr: &[Expr],
|
||||||
constants: &[Dynamic],
|
constants: &[Dynamic],
|
||||||
hashes: FnCallHashes,
|
hashes: FnCallHashes,
|
||||||
pos: Position,
|
|
||||||
capture_scope: bool,
|
capture_scope: bool,
|
||||||
|
pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let mut first_arg = first_arg;
|
let mut first_arg = first_arg;
|
||||||
@ -951,7 +1014,7 @@ impl Engine {
|
|||||||
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
||||||
let arg = first_arg.unwrap();
|
let arg = first_arg.unwrap();
|
||||||
let (arg_value, arg_pos) =
|
let (arg_value, arg_pos) =
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
|
||||||
|
|
||||||
if !arg_value.is::<FnPtr>() {
|
if !arg_value.is::<FnPtr>() {
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||||
@ -986,7 +1049,7 @@ impl Engine {
|
|||||||
KEYWORD_FN_PTR if total_args == 1 => {
|
KEYWORD_FN_PTR if total_args == 1 => {
|
||||||
let arg = first_arg.unwrap();
|
let arg = first_arg.unwrap();
|
||||||
let (arg_value, arg_pos) =
|
let (arg_value, arg_pos) =
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
|
||||||
|
|
||||||
// Fn - only in function call style
|
// Fn - only in function call style
|
||||||
return arg_value
|
return arg_value
|
||||||
@ -1001,7 +1064,7 @@ impl Engine {
|
|||||||
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
|
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
|
||||||
let first = first_arg.unwrap();
|
let first = first_arg.unwrap();
|
||||||
let (arg_value, arg_pos) = self
|
let (arg_value, arg_pos) = self
|
||||||
.get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?;
|
.get_arg_value(scope, global, state, lib, this_ptr, first, constants, level)?;
|
||||||
|
|
||||||
if !arg_value.is::<FnPtr>() {
|
if !arg_value.is::<FnPtr>() {
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||||
@ -1018,7 +1081,7 @@ impl Engine {
|
|||||||
.iter()
|
.iter()
|
||||||
.try_fold(fn_curry, |mut curried, expr| -> RhaiResultOf<_> {
|
.try_fold(fn_curry, |mut curried, expr| -> RhaiResultOf<_> {
|
||||||
let (value, _) = self.get_arg_value(
|
let (value, _) = self.get_arg_value(
|
||||||
scope, global, state, lib, this_ptr, level, expr, constants,
|
scope, global, state, lib, this_ptr, expr, constants, level,
|
||||||
)?;
|
)?;
|
||||||
curried.push(value);
|
curried.push(value);
|
||||||
Ok(curried)
|
Ok(curried)
|
||||||
@ -1032,7 +1095,7 @@ impl Engine {
|
|||||||
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
|
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
|
||||||
let arg = first_arg.unwrap();
|
let arg = first_arg.unwrap();
|
||||||
let (arg_value, _) =
|
let (arg_value, _) =
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
|
||||||
return Ok(arg_value.is_shared().into());
|
return Ok(arg_value.is_shared().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1041,14 +1104,14 @@ impl Engine {
|
|||||||
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
|
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
|
||||||
let first = first_arg.unwrap();
|
let first = first_arg.unwrap();
|
||||||
let (arg_value, arg_pos) = self
|
let (arg_value, arg_pos) = self
|
||||||
.get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?;
|
.get_arg_value(scope, global, state, lib, this_ptr, first, constants, level)?;
|
||||||
|
|
||||||
let fn_name = arg_value
|
let fn_name = arg_value
|
||||||
.into_immutable_string()
|
.into_immutable_string()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
|
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
|
||||||
|
|
||||||
let (arg_value, arg_pos) = self.get_arg_value(
|
let (arg_value, arg_pos) = self.get_arg_value(
|
||||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
scope, global, state, lib, this_ptr, &a_expr[0], constants, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let num_params = arg_value
|
let num_params = arg_value
|
||||||
@ -1068,7 +1131,7 @@ impl Engine {
|
|||||||
KEYWORD_IS_DEF_VAR if total_args == 1 => {
|
KEYWORD_IS_DEF_VAR if total_args == 1 => {
|
||||||
let arg = first_arg.unwrap();
|
let arg = first_arg.unwrap();
|
||||||
let (arg_value, arg_pos) =
|
let (arg_value, arg_pos) =
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
|
||||||
let var_name = arg_value
|
let var_name = arg_value
|
||||||
.into_immutable_string()
|
.into_immutable_string()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
|
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
|
||||||
@ -1081,7 +1144,7 @@ impl Engine {
|
|||||||
let orig_scope_len = scope.len();
|
let orig_scope_len = scope.len();
|
||||||
let arg = first_arg.unwrap();
|
let arg = first_arg.unwrap();
|
||||||
let (arg_value, pos) =
|
let (arg_value, pos) =
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
|
||||||
let script = &arg_value
|
let script = &arg_value
|
||||||
.into_immutable_string()
|
.into_immutable_string()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
|
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
|
||||||
@ -1130,7 +1193,7 @@ impl Engine {
|
|||||||
.map(|&v| v)
|
.map(|&v| v)
|
||||||
.chain(a_expr.iter())
|
.chain(a_expr.iter())
|
||||||
.try_for_each(|expr| {
|
.try_for_each(|expr| {
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
|
||||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||||
})?;
|
})?;
|
||||||
args.extend(curry.iter_mut());
|
args.extend(curry.iter_mut());
|
||||||
@ -1141,7 +1204,7 @@ impl Engine {
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, scope,
|
scope, global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v);
|
.map(|(v, _)| v);
|
||||||
@ -1162,12 +1225,12 @@ impl Engine {
|
|||||||
|
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
a_expr.iter().try_for_each(|expr| {
|
a_expr.iter().try_for_each(|expr| {
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
|
||||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (mut target, _pos) =
|
let (mut target, _pos) =
|
||||||
self.search_namespace(scope, global, state, lib, this_ptr, first_expr)?;
|
self.search_namespace(scope, global, state, lib, this_ptr, first_expr, level)?;
|
||||||
|
|
||||||
if target.as_ref().is_read_only() {
|
if target.as_ref().is_read_only() {
|
||||||
target = target.into_owned();
|
target = target.into_owned();
|
||||||
@ -1198,7 +1261,7 @@ impl Engine {
|
|||||||
.chain(a_expr.iter())
|
.chain(a_expr.iter())
|
||||||
.try_for_each(|expr| {
|
.try_for_each(|expr| {
|
||||||
self.get_arg_value(
|
self.get_arg_value(
|
||||||
scope, global, state, lib, this_ptr, level, expr, constants,
|
scope, global, state, lib, this_ptr, expr, constants, level,
|
||||||
)
|
)
|
||||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||||
})?;
|
})?;
|
||||||
@ -1208,7 +1271,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, None, level,
|
None, global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
@ -1248,13 +1311,14 @@ impl Engine {
|
|||||||
arg_values.push(Dynamic::UNIT);
|
arg_values.push(Dynamic::UNIT);
|
||||||
|
|
||||||
args_expr.iter().skip(1).try_for_each(|expr| {
|
args_expr.iter().skip(1).try_for_each(|expr| {
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
|
||||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Get target reference to first argument
|
// Get target reference to first argument
|
||||||
|
let first_arg = &args_expr[0];
|
||||||
let (target, _pos) =
|
let (target, _pos) =
|
||||||
self.search_scope_only(scope, global, state, lib, this_ptr, &args_expr[0])?;
|
self.search_scope_only(scope, global, state, lib, this_ptr, first_arg, level)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, _pos)?;
|
self.inc_operations(&mut global.num_operations, _pos)?;
|
||||||
@ -1278,7 +1342,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
// func(..., ...) or func(mod::x, ...)
|
// func(..., ...) or func(mod::x, ...)
|
||||||
args_expr.iter().try_for_each(|expr| {
|
args_expr.iter().try_for_each(|expr| {
|
||||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
|
||||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||||
})?;
|
})?;
|
||||||
args.extend(arg_values.iter_mut());
|
args.extend(arg_values.iter_mut());
|
||||||
@ -1312,34 +1376,27 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let level = level + 1;
|
||||||
|
|
||||||
match func {
|
match func {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Some(f) if f.is_script() => {
|
Some(f) if f.is_script() => {
|
||||||
let fn_def = f.get_script_fn_def().expect("script-defined function");
|
let fn_def = f.get_script_fn_def().expect("script-defined function");
|
||||||
|
let new_scope = &mut Scope::new();
|
||||||
|
let mut source = module.id_raw().clone();
|
||||||
|
mem::swap(&mut global.source, &mut source);
|
||||||
|
|
||||||
if fn_def.body.is_empty() {
|
let result = self.call_script_fn(
|
||||||
Ok(Dynamic::UNIT)
|
new_scope, global, state, lib, &mut None, fn_def, &mut args, true, pos, level,
|
||||||
} else {
|
);
|
||||||
let new_scope = &mut Scope::new();
|
|
||||||
|
|
||||||
let mut source = module.id_raw().clone();
|
global.source = source;
|
||||||
mem::swap(&mut global.source, &mut source);
|
|
||||||
|
|
||||||
let level = level + 1;
|
result
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
|
||||||
new_scope, global, state, lib, &mut None, fn_def, &mut args, pos, true,
|
|
||||||
level,
|
|
||||||
);
|
|
||||||
|
|
||||||
global.source = source;
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(f) if f.is_plugin_fn() => {
|
Some(f) if f.is_plugin_fn() => {
|
||||||
let context = (self, fn_name, module.id(), &*global, lib, pos).into();
|
let context = (self, fn_name, module.id(), &*global, lib, pos, level).into();
|
||||||
let result = f
|
let result = f
|
||||||
.get_plugin_fn()
|
.get_plugin_fn()
|
||||||
.expect("plugin function")
|
.expect("plugin function")
|
||||||
@ -1350,7 +1407,7 @@ impl Engine {
|
|||||||
|
|
||||||
Some(f) if f.is_native() => {
|
Some(f) if f.is_native() => {
|
||||||
let func = f.get_native_fn().expect("native function");
|
let func = f.get_native_fn().expect("native function");
|
||||||
let context = (self, fn_name, module.id(), &*global, lib, pos).into();
|
let context = (self, fn_name, module.id(), &*global, lib, pos, level).into();
|
||||||
let result = func(context, &mut args);
|
let result = func(context, &mut args);
|
||||||
self.check_return_value(result, pos)
|
self.check_return_value(result, pos)
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,8 @@ pub struct NativeCallContext<'a> {
|
|||||||
lib: &'a [&'a Module],
|
lib: &'a [&'a Module],
|
||||||
/// [Position] of the function call.
|
/// [Position] of the function call.
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
/// The current nesting level of function calls.
|
||||||
|
level: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
||||||
@ -80,6 +82,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
|||||||
&'a GlobalRuntimeState<'a>,
|
&'a GlobalRuntimeState<'a>,
|
||||||
&'a M,
|
&'a M,
|
||||||
Position,
|
Position,
|
||||||
|
usize,
|
||||||
)> for NativeCallContext<'a>
|
)> for NativeCallContext<'a>
|
||||||
{
|
{
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -91,6 +94,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
|||||||
&'a GlobalRuntimeState,
|
&'a GlobalRuntimeState,
|
||||||
&'a M,
|
&'a M,
|
||||||
Position,
|
Position,
|
||||||
|
usize,
|
||||||
),
|
),
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -100,6 +104,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
|||||||
global: Some(value.3),
|
global: Some(value.3),
|
||||||
lib: value.4.as_ref(),
|
lib: value.4.as_ref(),
|
||||||
pos: value.5,
|
pos: value.5,
|
||||||
|
level: value.6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,6 +121,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
|||||||
global: None,
|
global: None,
|
||||||
lib: value.2.as_ref(),
|
lib: value.2.as_ref(),
|
||||||
pos: Position::NONE,
|
pos: Position::NONE,
|
||||||
|
level: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +147,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
global: None,
|
global: None,
|
||||||
lib,
|
lib,
|
||||||
pos: Position::NONE,
|
pos: Position::NONE,
|
||||||
|
level: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// _(internals)_ Create a new [`NativeCallContext`].
|
/// _(internals)_ Create a new [`NativeCallContext`].
|
||||||
@ -158,6 +165,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
global: &'a GlobalRuntimeState,
|
global: &'a GlobalRuntimeState,
|
||||||
lib: &'a [&Module],
|
lib: &'a [&Module],
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
level: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
@ -166,6 +174,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
global: Some(global),
|
global: Some(global),
|
||||||
lib,
|
lib,
|
||||||
pos,
|
pos,
|
||||||
|
level,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The current [`Engine`].
|
/// The current [`Engine`].
|
||||||
@ -186,6 +195,12 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
pub const fn position(&self) -> Position {
|
pub const fn position(&self) -> Position {
|
||||||
self.pos
|
self.pos
|
||||||
}
|
}
|
||||||
|
/// Current nesting level of function calls.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn call_level(&self) -> usize {
|
||||||
|
self.level
|
||||||
|
}
|
||||||
/// The current source.
|
/// The current source.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -306,6 +321,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
|
|
||||||
self.engine()
|
self.engine()
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
|
None,
|
||||||
&mut global,
|
&mut global,
|
||||||
&mut state,
|
&mut state,
|
||||||
self.lib,
|
self.lib,
|
||||||
@ -315,8 +331,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
is_ref_mut,
|
is_ref_mut,
|
||||||
is_method_call,
|
is_method_call,
|
||||||
Position::NONE,
|
Position::NONE,
|
||||||
None,
|
self.level + 1,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
.map(|(r, _)| r)
|
.map(|(r, _)| r)
|
||||||
}
|
}
|
||||||
@ -429,3 +444,11 @@ pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Optio
|
|||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type OnVarCallback =
|
pub type OnVarCallback =
|
||||||
dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync;
|
dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync;
|
||||||
|
|
||||||
|
/// Callback function for variable definition.
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
pub type OnDefVarCallback = dyn Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool>;
|
||||||
|
/// Callback function for variable definition.
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
pub type OnDefVarCallback =
|
||||||
|
dyn Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool> + Send + Sync;
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
use super::call::FnCallArgs;
|
use super::call::FnCallArgs;
|
||||||
use crate::ast::ScriptFnDef;
|
use crate::ast::ScriptFnDef;
|
||||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||||
use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, StaticVec, ERR};
|
use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, ERR};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
|
/// # Main Entry-Point
|
||||||
|
///
|
||||||
/// Call a script-defined function.
|
/// Call a script-defined function.
|
||||||
///
|
///
|
||||||
/// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not.
|
/// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not.
|
||||||
@ -29,8 +31,8 @@ impl Engine {
|
|||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
fn_def: &ScriptFnDef,
|
fn_def: &ScriptFnDef,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
pos: Position,
|
|
||||||
rewind_scope: bool,
|
rewind_scope: bool,
|
||||||
|
pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
@ -41,13 +43,19 @@ impl Engine {
|
|||||||
err: RhaiError,
|
err: RhaiError,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
|
let _fn_def = fn_def;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
let source = _fn_def
|
||||||
|
.environ
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|environ| environ.lib.id().map(str::to_string));
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
let source = None;
|
||||||
|
|
||||||
Err(ERR::ErrorInFunctionCall(
|
Err(ERR::ErrorInFunctionCall(
|
||||||
name,
|
name,
|
||||||
fn_def
|
source.unwrap_or_else(|| global.source.to_string()),
|
||||||
.lib
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|m| m.id().map(str::to_string))
|
|
||||||
.unwrap_or_else(|| global.source.to_string()),
|
|
||||||
err,
|
err,
|
||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
@ -59,22 +67,26 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, pos)?;
|
self.inc_operations(&mut global.num_operations, pos)?;
|
||||||
|
|
||||||
if fn_def.body.is_empty() {
|
|
||||||
return Ok(Dynamic::UNIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for stack overflow
|
// Check for stack overflow
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if level > self.max_call_levels() {
|
if level > self.max_call_levels() {
|
||||||
return Err(ERR::ErrorStackOverflow(pos).into());
|
return Err(ERR::ErrorStackOverflow(pos).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_none() && fn_def.body.is_empty() {
|
||||||
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "debugging"))]
|
||||||
|
if fn_def.body.is_empty() {
|
||||||
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
|
||||||
let orig_scope_len = scope.len();
|
let orig_scope_len = scope.len();
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let orig_imports_len = global.num_imports();
|
let orig_imports_len = global.num_imports();
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let orig_call_stack_len = global.debugger.call_stack().len();
|
let orig_call_stack_len = global.debugger.call_stack().len();
|
||||||
|
|
||||||
// Put arguments into scope as variables
|
// Put arguments into scope as variables
|
||||||
@ -85,44 +97,58 @@ impl Engine {
|
|||||||
|
|
||||||
// Push a new call stack frame
|
// Push a new call stack frame
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
#[cfg(not(feature = "no_function"))]
|
if self.debugger.is_some() {
|
||||||
global.debugger.push_call_stack_frame(
|
global.debugger.push_call_stack_frame(
|
||||||
fn_def.name.clone(),
|
fn_def.name.clone(),
|
||||||
scope
|
scope
|
||||||
.iter()
|
.iter()
|
||||||
.skip(orig_scope_len)
|
.skip(orig_scope_len)
|
||||||
.map(|(_, _, v)| v.clone())
|
.map(|(_, _, v)| v.clone())
|
||||||
.collect(),
|
.collect(),
|
||||||
global.source.clone(),
|
global.source.clone(),
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Merge in encapsulated environment, if any
|
// Merge in encapsulated environment, if any
|
||||||
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
|
|
||||||
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
||||||
|
|
||||||
let lib = if let Some(ref fn_lib) = fn_def.lib {
|
#[cfg(not(feature = "no_module"))]
|
||||||
if fn_lib.is_empty() {
|
let mut lib_merged = crate::StaticVec::with_capacity(lib.len() + 1);
|
||||||
lib
|
|
||||||
} else {
|
|
||||||
state.push_fn_resolution_cache();
|
|
||||||
lib_merged.push(fn_lib.as_ref());
|
|
||||||
lib_merged.extend(lib.iter().cloned());
|
|
||||||
&lib_merged
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lib
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if let Some(ref modules) = fn_def.global {
|
let (lib, constants) = if let Some(crate::ast::EncapsulatedEnviron {
|
||||||
for (n, m) in modules.iter().cloned() {
|
lib: ref fn_lib,
|
||||||
|
ref imports,
|
||||||
|
ref constants,
|
||||||
|
}) = fn_def.environ
|
||||||
|
{
|
||||||
|
for (n, m) in imports.iter().cloned() {
|
||||||
global.push_import(n, m)
|
global.push_import(n, m)
|
||||||
}
|
}
|
||||||
|
(
|
||||||
|
if fn_lib.is_empty() {
|
||||||
|
lib
|
||||||
|
} else {
|
||||||
|
state.push_fn_resolution_cache();
|
||||||
|
lib_merged.push(fn_lib.as_ref());
|
||||||
|
lib_merged.extend(lib.iter().cloned());
|
||||||
|
&lib_merged
|
||||||
|
},
|
||||||
|
Some(mem::replace(&mut global.constants, constants.clone())),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(lib, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
{
|
||||||
|
let node = crate::ast::Stmt::Noop(fn_def.body.position());
|
||||||
|
self.run_debugger(scope, global, state, lib, this_ptr, &node, level)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the function
|
// Evaluate the function
|
||||||
let result = self
|
let mut _result = self
|
||||||
.eval_stmt_block(
|
.eval_stmt_block(
|
||||||
scope,
|
scope,
|
||||||
global,
|
global,
|
||||||
@ -155,6 +181,31 @@ impl Engine {
|
|||||||
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos),
|
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
{
|
||||||
|
let trigger = match global.debugger.status {
|
||||||
|
crate::eval::DebuggerStatus::FunctionExit(n) => n >= level,
|
||||||
|
crate::eval::DebuggerStatus::Next(_, true) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if trigger {
|
||||||
|
let node = crate::ast::Stmt::Noop(fn_def.body.end_position().or_else(pos));
|
||||||
|
let node = (&node).into();
|
||||||
|
let event = match _result {
|
||||||
|
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
|
||||||
|
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
|
||||||
|
};
|
||||||
|
match self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
|
||||||
|
{
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => _result = Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the call stack
|
||||||
|
global.debugger.rewind_call_stack(orig_call_stack_len);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove all local variables and imported modules
|
// Remove all local variables and imported modules
|
||||||
if rewind_scope {
|
if rewind_scope {
|
||||||
scope.rewind(orig_scope_len);
|
scope.rewind(orig_scope_len);
|
||||||
@ -165,15 +216,16 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
global.truncate_imports(orig_imports_len);
|
global.truncate_imports(orig_imports_len);
|
||||||
|
|
||||||
|
// Restore constants
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
if let Some(constants) = constants {
|
||||||
|
global.constants = constants;
|
||||||
|
}
|
||||||
|
|
||||||
// Restore state
|
// Restore state
|
||||||
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
|
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
|
||||||
|
|
||||||
// Pop the call stack
|
_result
|
||||||
#[cfg(feature = "debugging")]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
global.debugger.rewind_call_stack(orig_call_stack_len);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does a script-defined function exist?
|
// Does a script-defined function exist?
|
||||||
|
11
src/lib.rs
11
src/lib.rs
@ -154,6 +154,8 @@ pub use eval::EvalContext;
|
|||||||
pub use func::{NativeCallContext, RegisterNativeFunction};
|
pub use func::{NativeCallContext, RegisterNativeFunction};
|
||||||
pub use module::{FnNamespace, Module};
|
pub use module::{FnNamespace, Module};
|
||||||
pub use tokenizer::Position;
|
pub use tokenizer::Position;
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
pub use types::Instant;
|
||||||
pub use types::{
|
pub use types::{
|
||||||
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
|
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
|
||||||
};
|
};
|
||||||
@ -164,7 +166,7 @@ pub use types::{
|
|||||||
pub mod debugger {
|
pub mod debugger {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use super::eval::CallStackFrame;
|
pub use super::eval::CallStackFrame;
|
||||||
pub use super::eval::{BreakPoint, Debugger, DebuggerCommand};
|
pub use super::eval::{BreakPoint, Debugger, DebuggerCommand, DebuggerEvent};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
|
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
|
||||||
@ -256,9 +258,14 @@ pub use parser::ParseState;
|
|||||||
pub use ast::{
|
pub use ast::{
|
||||||
ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||||
OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
||||||
AST_OPTION_FLAGS::*,
|
AST_OPTION_FLAGS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub use ast::EncapsulatedEnviron;
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub use ast::FloatWrapper;
|
pub use ast::FloatWrapper;
|
||||||
|
@ -1696,33 +1696,21 @@ impl Module {
|
|||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
|
|
||||||
// Extra modules left become sub-modules
|
// Extra modules left become sub-modules
|
||||||
#[cfg(not(feature = "no_function"))]
|
let mut imports = StaticVec::new_const();
|
||||||
let mut func_global = None;
|
|
||||||
|
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
global
|
global
|
||||||
.scan_imports_raw()
|
.scan_imports_raw()
|
||||||
.skip(orig_imports_len)
|
.skip(orig_imports_len)
|
||||||
.for_each(|(k, m)| {
|
.for_each(|(k, m)| {
|
||||||
#[cfg(not(feature = "no_function"))]
|
imports.push((k.clone(), m.clone()));
|
||||||
if func_global.is_none() {
|
|
||||||
func_global = Some(StaticVec::new());
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
func_global
|
|
||||||
.as_mut()
|
|
||||||
.expect("`Some`")
|
|
||||||
.push((k.clone(), m.clone()));
|
|
||||||
|
|
||||||
module.set_sub_module(k.clone(), m.clone());
|
module.set_sub_module(k.clone(), m.clone());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore global state
|
// Restore global state
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
let constants = std::mem::replace(&mut global.constants, orig_constants);
|
||||||
global.constants = orig_constants;
|
|
||||||
}
|
|
||||||
global.truncate_imports(orig_imports_len);
|
global.truncate_imports(orig_imports_len);
|
||||||
global.source = orig_source;
|
global.source = orig_source;
|
||||||
|
|
||||||
@ -1747,12 +1735,9 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let func_global = func_global.map(|v| v.into_boxed_slice());
|
|
||||||
|
|
||||||
// Non-private functions defined become module functions
|
// Non-private functions defined become module functions
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if ast.has_functions() {
|
{
|
||||||
ast.shared_lib()
|
ast.shared_lib()
|
||||||
.iter_fn()
|
.iter_fn()
|
||||||
.filter(|&f| match f.metadata.access {
|
.filter(|&f| match f.metadata.access {
|
||||||
@ -1761,15 +1746,20 @@ impl Module {
|
|||||||
})
|
})
|
||||||
.filter(|&f| f.func.is_script())
|
.filter(|&f| f.func.is_script())
|
||||||
.for_each(|f| {
|
.for_each(|f| {
|
||||||
// Encapsulate AST environment
|
|
||||||
let mut func = f
|
let mut func = f
|
||||||
.func
|
.func
|
||||||
.get_script_fn_def()
|
.get_script_fn_def()
|
||||||
.expect("script-defined function")
|
.expect("script-defined function")
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.clone();
|
.clone();
|
||||||
func.lib = Some(ast.shared_lib().clone());
|
|
||||||
func.global = func_global.clone();
|
// Encapsulate AST environment
|
||||||
|
func.environ = Some(crate::ast::EncapsulatedEnviron {
|
||||||
|
lib: ast.shared_lib().clone(),
|
||||||
|
imports: imports.clone().into_boxed_slice(),
|
||||||
|
constants: constants.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
module.set_script_fn(func);
|
module.set_script_fn(func);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,7 @@ impl<'a> OptimizerState<'a> {
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Position::NONE,
|
Position::NONE,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
@ -462,13 +463,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
|
Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
let pos = condition.position();
|
let pos = condition.start_position();
|
||||||
let mut expr = mem::take(condition);
|
let mut expr = mem::take(condition);
|
||||||
optimize_expr(&mut expr, state, false);
|
optimize_expr(&mut expr, state, false);
|
||||||
|
|
||||||
*stmt = if preserve_result {
|
*stmt = if preserve_result {
|
||||||
// -> { expr, Noop }
|
// -> { expr, Noop }
|
||||||
Stmt::Block([Stmt::Expr(expr), Stmt::Noop(pos)].into(), pos)
|
Stmt::Block(
|
||||||
|
[Stmt::Expr(expr), Stmt::Noop(pos)].into(),
|
||||||
|
(pos, Position::NONE),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// -> expr
|
// -> expr
|
||||||
Stmt::Expr(expr)
|
Stmt::Expr(expr)
|
||||||
@ -486,7 +490,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
|
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
|
||||||
{
|
{
|
||||||
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
|
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
|
||||||
statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()),
|
statements => Stmt::Block(statements.into_boxed_slice(), x.1.positions()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if true { if_block } else { else_block } -> if_block
|
// if true { if_block } else { else_block } -> if_block
|
||||||
@ -496,7 +500,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false)
|
match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false)
|
||||||
{
|
{
|
||||||
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
|
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
|
||||||
statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()),
|
statements => Stmt::Block(statements.into_boxed_slice(), x.0.positions()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if expr { if_block } else { else_block }
|
// if expr { if_block } else { else_block }
|
||||||
@ -530,11 +534,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
mem::take(&mut block.statements),
|
mem::take(&mut block.statements),
|
||||||
Stmt::Block(
|
Stmt::Block(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt.into_boxed_slice(),
|
||||||
x.def_case.position().or_else(*pos),
|
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
)),
|
||||||
match_expr.position(),
|
match_expr.start_position(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Promote the matched case
|
// Promote the matched case
|
||||||
@ -545,7 +549,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.position());
|
*stmt =
|
||||||
|
Stmt::Block(statements.into_boxed_slice(), block.statements.positions());
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -585,11 +590,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
mem::take(&mut block.statements),
|
mem::take(&mut block.statements),
|
||||||
Stmt::Block(
|
Stmt::Block(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt.into_boxed_slice(),
|
||||||
x.def_case.position().or_else(*pos),
|
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
)),
|
||||||
match_expr.position(),
|
match_expr.start_position(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Promote the matched case
|
// Promote the matched case
|
||||||
@ -598,7 +603,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
optimize_stmt_block(statements, state, true, true, false);
|
optimize_stmt_block(statements, state, true, true, false);
|
||||||
*stmt = Stmt::Block(
|
*stmt = Stmt::Block(
|
||||||
statements.into_boxed_slice(),
|
statements.into_boxed_slice(),
|
||||||
block.statements.position(),
|
block.statements.positions(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -646,7 +651,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
||||||
*stmt = Stmt::Block(
|
*stmt = Stmt::Block(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt.into_boxed_slice(),
|
||||||
x.def_case.position().or_else(*pos),
|
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// switch
|
// switch
|
||||||
@ -703,7 +708,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
if preserve_result {
|
if preserve_result {
|
||||||
statements.push(Stmt::Noop(pos))
|
statements.push(Stmt::Noop(pos))
|
||||||
}
|
}
|
||||||
*stmt = Stmt::Block(statements.into_boxed_slice(), pos);
|
*stmt =
|
||||||
|
Stmt::Block(statements.into_boxed_slice(), (pos, Position::NONE));
|
||||||
} else {
|
} else {
|
||||||
*stmt = Stmt::Noop(pos);
|
*stmt = Stmt::Noop(pos);
|
||||||
};
|
};
|
||||||
@ -720,7 +726,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
*stmt = Stmt::Block(
|
*stmt = Stmt::Block(
|
||||||
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
|
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
|
||||||
.into_boxed_slice(),
|
.into_boxed_slice(),
|
||||||
body.position(),
|
body.positions(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// do { block } while|until expr
|
// do { block } while|until expr
|
||||||
@ -748,7 +754,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
match block.as_mut_slice() {
|
match block.as_mut_slice() {
|
||||||
[] => {
|
[] => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::Noop(*pos);
|
*stmt = Stmt::Noop(pos.0);
|
||||||
}
|
}
|
||||||
// Only one statement which is not block-dependent - promote
|
// Only one statement which is not block-dependent - promote
|
||||||
[s] if !s.is_block_dependent() => {
|
[s] if !s.is_block_dependent() => {
|
||||||
@ -765,7 +771,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
*stmt = Stmt::Block(
|
*stmt = Stmt::Block(
|
||||||
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
|
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
|
||||||
.into_boxed_slice(),
|
.into_boxed_slice(),
|
||||||
x.try_block.position(),
|
x.try_block.positions(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// try { try_block } catch ( var ) { catch_block }
|
// try { try_block } catch ( var ) { catch_block }
|
||||||
@ -1072,7 +1078,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
if let Some(value) = arg.get_literal_value() {
|
if let Some(value) = arg.get_literal_value() {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
constants.push(value);
|
constants.push(value);
|
||||||
*arg = Expr::Stack(constants.len()-1, arg.position());
|
*arg = Expr::Stack(constants.len()-1, arg.start_position());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1120,7 +1126,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
if let Some(value) = arg.get_literal_value() {
|
if let Some(value) = arg.get_literal_value() {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
x.constants.push(value);
|
x.constants.push(value);
|
||||||
*arg = Expr::Stack(x.constants.len()-1, arg.position());
|
*arg = Expr::Stack(x.constants.len()-1, arg.start_position());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1211,9 +1217,8 @@ pub fn optimize_into_ast(
|
|||||||
access: fn_def.access,
|
access: fn_def.access,
|
||||||
body: crate::ast::StmtBlock::NONE,
|
body: crate::ast::StmtBlock::NONE,
|
||||||
params: fn_def.params.clone(),
|
params: fn_def.params.clone(),
|
||||||
lib: None,
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
global: None,
|
environ: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
comments: None,
|
comments: None,
|
||||||
|
@ -27,15 +27,23 @@ def_package! {
|
|||||||
|
|
||||||
#[export_module]
|
#[export_module]
|
||||||
mod debugging_functions {
|
mod debugging_functions {
|
||||||
|
/// Get an array of object maps containing the function calls stack.
|
||||||
|
///
|
||||||
|
/// If there is no debugging interface registered, an empty array is returned.
|
||||||
|
///
|
||||||
|
/// An array of strings is returned under `no_object`.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub fn stack_trace(ctx: NativeCallContext) -> Array {
|
pub fn back_trace(ctx: NativeCallContext) -> Array {
|
||||||
if let Some(global) = ctx.global_runtime_state() {
|
if let Some(global) = ctx.global_runtime_state() {
|
||||||
global
|
global
|
||||||
.debugger
|
.debugger
|
||||||
.call_stack()
|
.call_stack()
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
|
.filter(|crate::debugger::CallStackFrame { fn_name, args, .. }| {
|
||||||
|
fn_name != "back_trace" || !args.is_empty()
|
||||||
|
})
|
||||||
.map(
|
.map(
|
||||||
|frame @ crate::debugger::CallStackFrame {
|
|frame @ crate::debugger::CallStackFrame {
|
||||||
fn_name: _fn_name,
|
fn_name: _fn_name,
|
||||||
|
154
src/parser.rs
154
src/parser.rs
@ -52,11 +52,11 @@ pub struct ParseState<'e> {
|
|||||||
pub entry_stack_len: usize,
|
pub entry_stack_len: usize,
|
||||||
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
pub external_vars: BTreeMap<Identifier, Position>,
|
pub external_vars: Vec<crate::ast::Ident>,
|
||||||
/// An indicator that disables variable capturing into externals one single time
|
/// An indicator that disables variable capturing into externals one single time
|
||||||
/// up until the nearest consumed Identifier token.
|
/// up until the nearest consumed Identifier token.
|
||||||
/// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable.
|
/// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable.
|
||||||
/// All consequent calls to [`access_var`][ParseState::access_var] will not be affected
|
/// All consequent calls to [`access_var`][ParseState::access_var] will not be affected.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
pub allow_capture: bool,
|
pub allow_capture: bool,
|
||||||
/// Encapsulates a local stack with imported [module][crate::Module] names.
|
/// Encapsulates a local stack with imported [module][crate::Module] names.
|
||||||
@ -85,7 +85,7 @@ impl<'e> ParseState<'e> {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()),
|
max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
external_vars: BTreeMap::new(),
|
external_vars: Vec::new(),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
allow_capture: true,
|
allow_capture: true,
|
||||||
interned_strings: StringsInterner::new(),
|
interned_strings: StringsInterner::new(),
|
||||||
@ -128,8 +128,11 @@ impl<'e> ParseState<'e> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
if self.allow_capture {
|
if self.allow_capture {
|
||||||
if index.is_none() && !self.external_vars.contains_key(name) {
|
if index.is_none() && !self.external_vars.iter().any(|v| v.name == name) {
|
||||||
self.external_vars.insert(name.into(), _pos);
|
self.external_vars.push(crate::ast::Ident {
|
||||||
|
name: name.into(),
|
||||||
|
pos: _pos,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.allow_capture = true
|
self.allow_capture = true
|
||||||
@ -303,7 +306,7 @@ impl Expr {
|
|||||||
|
|
||||||
Err(
|
Err(
|
||||||
PERR::MismatchedType("a boolean expression".to_string(), type_name.to_string())
|
PERR::MismatchedType("a boolean expression".to_string(), type_name.to_string())
|
||||||
.into_err(self.position()),
|
.into_err(self.start_position()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/// Raise an error if the expression can never yield an iterable value.
|
/// Raise an error if the expression can never yield an iterable value.
|
||||||
@ -323,7 +326,7 @@ impl Expr {
|
|||||||
|
|
||||||
Err(
|
Err(
|
||||||
PERR::MismatchedType("an iterable value".to_string(), type_name.to_string())
|
PERR::MismatchedType("an iterable value".to_string(), type_name.to_string())
|
||||||
.into_err(self.position()),
|
.into_err(self.start_position()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -518,6 +521,7 @@ fn parse_fn_call(
|
|||||||
namespace,
|
namespace,
|
||||||
hashes,
|
hashes,
|
||||||
args,
|
args,
|
||||||
|
pos: settings.pos,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(settings.pos));
|
.into_fn_call_expr(settings.pos));
|
||||||
@ -582,6 +586,7 @@ fn parse_fn_call(
|
|||||||
namespace,
|
namespace,
|
||||||
hashes,
|
hashes,
|
||||||
args,
|
args,
|
||||||
|
pos: settings.pos,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(settings.pos));
|
.into_fn_call_expr(settings.pos));
|
||||||
@ -649,7 +654,7 @@ fn parse_index_chain(
|
|||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Only arrays, object maps and strings can be indexed".into(),
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
)
|
)
|
||||||
.into_err(lhs.position()))
|
.into_err(lhs.start_position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::CharConstant(_, _)
|
Expr::CharConstant(_, _)
|
||||||
@ -660,7 +665,7 @@ fn parse_index_chain(
|
|||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Only arrays, object maps and strings can be indexed".into(),
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
)
|
)
|
||||||
.into_err(lhs.position()))
|
.into_err(lhs.start_position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -674,7 +679,7 @@ fn parse_index_chain(
|
|||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Array or string expects numeric index, not a string".into(),
|
"Array or string expects numeric index, not a string".into(),
|
||||||
)
|
)
|
||||||
.into_err(idx_expr.position()))
|
.into_err(idx_expr.start_position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
@ -682,7 +687,7 @@ fn parse_index_chain(
|
|||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Only arrays, object maps and strings can be indexed".into(),
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
)
|
)
|
||||||
.into_err(lhs.position()))
|
.into_err(lhs.start_position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::CharConstant(_, _)
|
Expr::CharConstant(_, _)
|
||||||
@ -693,7 +698,7 @@ fn parse_index_chain(
|
|||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Only arrays, object maps and strings can be indexed".into(),
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
)
|
)
|
||||||
.into_err(lhs.position()))
|
.into_err(lhs.start_position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -705,35 +710,35 @@ fn parse_index_chain(
|
|||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Array access expects integer index, not a float".into(),
|
"Array access expects integer index, not a float".into(),
|
||||||
)
|
)
|
||||||
.into_err(x.position()))
|
.into_err(x.start_position()))
|
||||||
}
|
}
|
||||||
// lhs[char]
|
// lhs[char]
|
||||||
x @ Expr::CharConstant(_, _) => {
|
x @ Expr::CharConstant(_, _) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Array access expects integer index, not a character".into(),
|
"Array access expects integer index, not a character".into(),
|
||||||
)
|
)
|
||||||
.into_err(x.position()))
|
.into_err(x.start_position()))
|
||||||
}
|
}
|
||||||
// lhs[()]
|
// lhs[()]
|
||||||
x @ Expr::Unit(_) => {
|
x @ Expr::Unit(_) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Array access expects integer index, not ()".into(),
|
"Array access expects integer index, not ()".into(),
|
||||||
)
|
)
|
||||||
.into_err(x.position()))
|
.into_err(x.start_position()))
|
||||||
}
|
}
|
||||||
// lhs[??? && ???], lhs[??? || ???]
|
// lhs[??? && ???], lhs[??? || ???]
|
||||||
x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
|
x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Array access expects integer index, not a boolean".into(),
|
"Array access expects integer index, not a boolean".into(),
|
||||||
)
|
)
|
||||||
.into_err(x.position()))
|
.into_err(x.start_position()))
|
||||||
}
|
}
|
||||||
// lhs[true], lhs[false]
|
// lhs[true], lhs[false]
|
||||||
x @ Expr::BoolConstant(_, _) => {
|
x @ Expr::BoolConstant(_, _) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Array access expects integer index, not a boolean".into(),
|
"Array access expects integer index, not a boolean".into(),
|
||||||
)
|
)
|
||||||
.into_err(x.position()))
|
.into_err(x.start_position()))
|
||||||
}
|
}
|
||||||
// All other expressions
|
// All other expressions
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -1045,7 +1050,7 @@ fn parse_switch(
|
|||||||
|
|
||||||
let (hash, range) = if let Some(expr) = expr {
|
let (hash, range) = if let Some(expr) = expr {
|
||||||
let value = expr.get_literal_value().ok_or_else(|| {
|
let value = expr.get_literal_value().ok_or_else(|| {
|
||||||
PERR::ExprExpected("a literal".to_string()).into_err(expr.position())
|
PERR::ExprExpected("a literal".to_string()).into_err(expr.start_position())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let guard = value.read_lock::<ExclusiveRange>();
|
let guard = value.read_lock::<ExclusiveRange>();
|
||||||
@ -1055,14 +1060,14 @@ fn parse_switch(
|
|||||||
} else if let Some(range) = value.read_lock::<InclusiveRange>() {
|
} else if let Some(range) = value.read_lock::<InclusiveRange>() {
|
||||||
(None, Some((*range.start(), *range.end(), true)))
|
(None, Some((*range.start(), *range.end(), true)))
|
||||||
} else if value.is::<INT>() && !ranges.is_empty() {
|
} else if value.is::<INT>() && !ranges.is_empty() {
|
||||||
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.position()));
|
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position()));
|
||||||
} else {
|
} else {
|
||||||
let hasher = &mut get_hasher();
|
let hasher = &mut get_hasher();
|
||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
if cases.contains_key(&hash) {
|
if cases.contains_key(&hash) {
|
||||||
return Err(PERR::DuplicatedSwitchCase.into_err(expr.position()));
|
return Err(PERR::DuplicatedSwitchCase.into_err(expr.start_position()));
|
||||||
}
|
}
|
||||||
(Some(hash), None)
|
(Some(hash), None)
|
||||||
}
|
}
|
||||||
@ -1258,12 +1263,14 @@ fn parse_primary(
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
new_state.external_vars.iter().try_for_each(
|
new_state.external_vars.iter().try_for_each(
|
||||||
|(captured_var, &pos)| -> ParseResult<_> {
|
|crate::ast::Ident { name, pos }| -> ParseResult<_> {
|
||||||
let index = state.access_var(captured_var, pos);
|
let index = state.access_var(name, *pos);
|
||||||
|
|
||||||
if !settings.is_closure && settings.strict_var && index.is_none() {
|
if settings.strict_var && !settings.is_closure && index.is_none() {
|
||||||
// If the parent scope is not inside another capturing closure
|
// If the parent scope is not inside another capturing closure
|
||||||
Err(PERR::VariableUndefined(captured_var.to_string()).into_err(pos))
|
// then we can conclude that the captured variable doesn't exist.
|
||||||
|
// Under Strict Variables mode, this is not allowed.
|
||||||
|
Err(PERR::VariableUndefined(name.to_string()).into_err(*pos))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1677,6 +1684,7 @@ fn parse_unary(
|
|||||||
name: state.get_identifier("", "-"),
|
name: state.get_identifier("", "-"),
|
||||||
hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
|
hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
|
||||||
args,
|
args,
|
||||||
|
pos,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(pos))
|
.into_fn_call_expr(pos))
|
||||||
@ -1703,6 +1711,7 @@ fn parse_unary(
|
|||||||
name: state.get_identifier("", "+"),
|
name: state.get_identifier("", "+"),
|
||||||
hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
|
hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
|
||||||
args,
|
args,
|
||||||
|
pos,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(pos))
|
.into_fn_call_expr(pos))
|
||||||
@ -1720,6 +1729,7 @@ fn parse_unary(
|
|||||||
name: state.get_identifier("", "!"),
|
name: state.get_identifier("", "!"),
|
||||||
hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
|
hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
|
||||||
args,
|
args,
|
||||||
|
pos,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(pos))
|
.into_fn_call_expr(pos))
|
||||||
@ -1748,7 +1758,7 @@ fn make_assignment_stmt(
|
|||||||
}
|
}
|
||||||
Expr::Property(_, _) => None,
|
Expr::Property(_, _) => None,
|
||||||
// Anything other than a property after dotting (e.g. a method call) is not an l-value
|
// Anything other than a property after dotting (e.g. a method call) is not an l-value
|
||||||
ref e => Some(e.position()),
|
ref e => Some(e.start_position()),
|
||||||
},
|
},
|
||||||
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
|
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
|
||||||
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
|
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
@ -1757,7 +1767,7 @@ fn make_assignment_stmt(
|
|||||||
},
|
},
|
||||||
Expr::Property(_, _) if parent_is_dot => None,
|
Expr::Property(_, _) if parent_is_dot => None,
|
||||||
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
|
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
e if parent_is_dot => Some(e.position()),
|
e if parent_is_dot => Some(e.start_position()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1767,7 +1777,7 @@ fn make_assignment_stmt(
|
|||||||
match lhs {
|
match lhs {
|
||||||
// const_expr = rhs
|
// const_expr = rhs
|
||||||
ref expr if expr.is_constant() => {
|
ref expr if expr.is_constant() => {
|
||||||
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
|
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.start_position()))
|
||||||
}
|
}
|
||||||
// var (non-indexed) = rhs
|
// var (non-indexed) = rhs
|
||||||
Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment(
|
Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment(
|
||||||
@ -1809,10 +1819,8 @@ fn make_assignment_stmt(
|
|||||||
op_pos,
|
op_pos,
|
||||||
)),
|
)),
|
||||||
// expr[???] = rhs, expr.??? = rhs
|
// expr[???] = rhs, expr.??? = rhs
|
||||||
ref expr => {
|
ref expr => Err(PERR::AssignmentToInvalidLHS("".to_string())
|
||||||
Err(PERR::AssignmentToInvalidLHS("".to_string())
|
.into_err(expr.start_position())),
|
||||||
.into_err(expr.position()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(err_pos) => {
|
Some(err_pos) => {
|
||||||
@ -1827,7 +1835,7 @@ fn make_assignment_stmt(
|
|||||||
)
|
)
|
||||||
.into_err(op_pos)),
|
.into_err(op_pos)),
|
||||||
// expr = rhs
|
// expr = rhs
|
||||||
_ => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(lhs.position())),
|
_ => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(lhs.start_position())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1978,7 +1986,7 @@ fn make_dot_expr(
|
|||||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
||||||
}
|
}
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
(_, rhs) => Err(PERR::PropertyExpected.into_err(rhs.position())),
|
(_, rhs) => Err(PERR::PropertyExpected.into_err(rhs.start_position())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2060,6 +2068,7 @@ fn parse_binary_op(
|
|||||||
let op_base = FnCallExpr {
|
let op_base = FnCallExpr {
|
||||||
name: state.get_identifier("", op),
|
name: state.get_identifier("", op),
|
||||||
hashes: FnCallHashes::from_native(hash),
|
hashes: FnCallHashes::from_native(hash),
|
||||||
|
pos,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2077,7 +2086,10 @@ fn parse_binary_op(
|
|||||||
| Token::LessThan
|
| Token::LessThan
|
||||||
| Token::LessThanEqualsTo
|
| Token::LessThanEqualsTo
|
||||||
| Token::GreaterThan
|
| Token::GreaterThan
|
||||||
| Token::GreaterThanEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos),
|
| Token::GreaterThanEqualsTo => {
|
||||||
|
let pos = args[0].start_position();
|
||||||
|
FnCallExpr { args, ..op_base }.into_fn_call_expr(pos)
|
||||||
|
}
|
||||||
|
|
||||||
Token::Or => {
|
Token::Or => {
|
||||||
let rhs = args.pop().unwrap();
|
let rhs = args.pop().unwrap();
|
||||||
@ -2106,6 +2118,7 @@ fn parse_binary_op(
|
|||||||
Token::In => {
|
Token::In => {
|
||||||
// Swap the arguments
|
// Swap the arguments
|
||||||
let current_lhs = args.remove(0);
|
let current_lhs = args.remove(0);
|
||||||
|
let pos = current_lhs.start_position();
|
||||||
args.push(current_lhs);
|
args.push(current_lhs);
|
||||||
args.shrink_to_fit();
|
args.shrink_to_fit();
|
||||||
|
|
||||||
@ -2127,6 +2140,7 @@ fn parse_binary_op(
|
|||||||
.map_or(false, Option::is_some) =>
|
.map_or(false, Option::is_some) =>
|
||||||
{
|
{
|
||||||
let hash = calc_fn_hash(&s, 2);
|
let hash = calc_fn_hash(&s, 2);
|
||||||
|
let pos = args[0].start_position();
|
||||||
|
|
||||||
FnCallExpr {
|
FnCallExpr {
|
||||||
hashes: if is_valid_function_name(&s) {
|
hashes: if is_valid_function_name(&s) {
|
||||||
@ -2140,7 +2154,10 @@ fn parse_binary_op(
|
|||||||
.into_fn_call_expr(pos)
|
.into_fn_call_expr(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos),
|
_ => {
|
||||||
|
let pos = args[0].start_position();
|
||||||
|
FnCallExpr { args, ..op_base }.into_fn_call_expr(pos)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2574,6 +2591,12 @@ fn parse_let(
|
|||||||
// let name ...
|
// let name ...
|
||||||
let (name, pos) = parse_var_name(input)?;
|
let (name, pos) = parse_var_name(input)?;
|
||||||
|
|
||||||
|
if !settings.default_options.allow_shadowing
|
||||||
|
&& state.stack.iter().any(|(v, _)| v == name.as_ref())
|
||||||
|
{
|
||||||
|
return Err(PERR::VariableExists(name.to_string()).into_err(pos));
|
||||||
|
}
|
||||||
|
|
||||||
let name = state.get_identifier("", name);
|
let name = state.get_identifier("", name);
|
||||||
let var_def = Ident {
|
let var_def = Ident {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
@ -2729,13 +2752,10 @@ fn parse_block(
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let orig_imports_len = state.imports.len();
|
let orig_imports_len = state.imports.len();
|
||||||
|
|
||||||
loop {
|
let end_pos = loop {
|
||||||
// Terminated?
|
// Terminated?
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
(Token::RightBrace, _) => {
|
(Token::RightBrace, _) => break eat_token(input, Token::RightBrace),
|
||||||
eat_token(input, Token::RightBrace);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
(Token::EOF, pos) => {
|
(Token::EOF, pos) => {
|
||||||
return Err(PERR::MissingToken(
|
return Err(PERR::MissingToken(
|
||||||
Token::RightBrace.into(),
|
Token::RightBrace.into(),
|
||||||
@ -2762,10 +2782,7 @@ fn parse_block(
|
|||||||
|
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
// { ... stmt }
|
// { ... stmt }
|
||||||
(Token::RightBrace, _) => {
|
(Token::RightBrace, _) => break eat_token(input, Token::RightBrace),
|
||||||
eat_token(input, Token::RightBrace);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// { ... stmt;
|
// { ... stmt;
|
||||||
(Token::SemiColon, _) if need_semicolon => {
|
(Token::SemiColon, _) if need_semicolon => {
|
||||||
eat_token(input, Token::SemiColon);
|
eat_token(input, Token::SemiColon);
|
||||||
@ -2788,7 +2805,7 @@ fn parse_block(
|
|||||||
.into_err(*pos));
|
.into_err(*pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
state.stack.truncate(state.entry_stack_len);
|
state.stack.truncate(state.entry_stack_len);
|
||||||
state.entry_stack_len = prev_entry_stack_len;
|
state.entry_stack_len = prev_entry_stack_len;
|
||||||
@ -2796,7 +2813,10 @@ fn parse_block(
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
state.imports.truncate(orig_imports_len);
|
state.imports.truncate(orig_imports_len);
|
||||||
|
|
||||||
Ok(Stmt::Block(statements.into_boxed_slice(), settings.pos))
|
Ok(Stmt::Block(
|
||||||
|
statements.into_boxed_slice(),
|
||||||
|
(settings.pos, end_pos),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression as a statement.
|
/// Parse an expression as a statement.
|
||||||
@ -2959,25 +2979,25 @@ fn parse_stmt(
|
|||||||
|
|
||||||
Token::If => parse_if(input, state, lib, settings.level_up()),
|
Token::If => parse_if(input, state, lib, settings.level_up()),
|
||||||
Token::Switch => parse_switch(input, state, lib, settings.level_up()),
|
Token::Switch => parse_switch(input, state, lib, settings.level_up()),
|
||||||
Token::While | Token::Loop if settings.default_options.allow_loop => {
|
Token::While | Token::Loop if settings.default_options.allow_looping => {
|
||||||
parse_while_loop(input, state, lib, settings.level_up())
|
parse_while_loop(input, state, lib, settings.level_up())
|
||||||
}
|
}
|
||||||
Token::Do if settings.default_options.allow_loop => {
|
Token::Do if settings.default_options.allow_looping => {
|
||||||
parse_do(input, state, lib, settings.level_up())
|
parse_do(input, state, lib, settings.level_up())
|
||||||
}
|
}
|
||||||
Token::For if settings.default_options.allow_loop => {
|
Token::For if settings.default_options.allow_looping => {
|
||||||
parse_for(input, state, lib, settings.level_up())
|
parse_for(input, state, lib, settings.level_up())
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Continue if settings.default_options.allow_loop && settings.is_breakable => {
|
Token::Continue if settings.default_options.allow_looping && settings.is_breakable => {
|
||||||
let pos = eat_token(input, Token::Continue);
|
let pos = eat_token(input, Token::Continue);
|
||||||
Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos))
|
Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos))
|
||||||
}
|
}
|
||||||
Token::Break if settings.default_options.allow_loop && settings.is_breakable => {
|
Token::Break if settings.default_options.allow_looping && settings.is_breakable => {
|
||||||
let pos = eat_token(input, Token::Break);
|
let pos = eat_token(input, Token::Break);
|
||||||
Ok(Stmt::BreakLoop(AST_OPTION_BREAK, pos))
|
Ok(Stmt::BreakLoop(AST_OPTION_BREAK, pos))
|
||||||
}
|
}
|
||||||
Token::Continue | Token::Break if settings.default_options.allow_loop => {
|
Token::Continue | Token::Break if settings.default_options.allow_looping => {
|
||||||
Err(PERR::LoopBreak.into_err(token_pos))
|
Err(PERR::LoopBreak.into_err(token_pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3185,9 +3205,8 @@ fn parse_fn(
|
|||||||
access,
|
access,
|
||||||
params,
|
params,
|
||||||
body,
|
body,
|
||||||
lib: None,
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
global: None,
|
environ: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
comments: if comments.is_empty() {
|
comments: if comments.is_empty() {
|
||||||
@ -3240,6 +3259,7 @@ fn make_curry_from_externals(
|
|||||||
num_externals + 1,
|
num_externals + 1,
|
||||||
)),
|
)),
|
||||||
args,
|
args,
|
||||||
|
pos,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(pos);
|
.into_fn_call_expr(pos);
|
||||||
@ -3249,7 +3269,7 @@ fn make_curry_from_externals(
|
|||||||
let mut statements = StaticVec::with_capacity(externals.len() + 1);
|
let mut statements = StaticVec::with_capacity(externals.len() + 1);
|
||||||
statements.extend(externals.into_iter().map(Stmt::Share));
|
statements.extend(externals.into_iter().map(Stmt::Share));
|
||||||
statements.push(Stmt::Expr(expr));
|
statements.push(Stmt::Expr(expr));
|
||||||
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos).into())
|
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an anonymous function definition.
|
/// Parse an anonymous function definition.
|
||||||
@ -3312,20 +3332,21 @@ fn parse_anon_fn(
|
|||||||
// External variables may need to be processed in a consistent order,
|
// External variables may need to be processed in a consistent order,
|
||||||
// so extract them into a list.
|
// so extract them into a list.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
let externals: StaticVec<Identifier> = state
|
let (mut params, externals) = {
|
||||||
.external_vars
|
let externals: StaticVec<Identifier> = state
|
||||||
.iter()
|
.external_vars
|
||||||
.map(|(name, _)| name.clone())
|
.iter()
|
||||||
.collect();
|
.map(|crate::ast::Ident { name, .. }| name.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
let mut params = StaticVec::with_capacity(params_list.len() + externals.len());
|
||||||
let mut params = StaticVec::with_capacity(params_list.len() + externals.len());
|
params.extend(externals.iter().cloned());
|
||||||
|
|
||||||
|
(params, externals)
|
||||||
|
};
|
||||||
#[cfg(feature = "no_closure")]
|
#[cfg(feature = "no_closure")]
|
||||||
let mut params = StaticVec::with_capacity(params_list.len());
|
let mut params = StaticVec::with_capacity(params_list.len());
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
params.extend(externals.iter().cloned());
|
|
||||||
|
|
||||||
params.append(&mut params_list);
|
params.append(&mut params_list);
|
||||||
|
|
||||||
// Create unique function name by hashing the script body plus the parameters.
|
// Create unique function name by hashing the script body plus the parameters.
|
||||||
@ -3341,9 +3362,8 @@ fn parse_anon_fn(
|
|||||||
access: crate::FnAccess::Public,
|
access: crate::FnAccess::Public,
|
||||||
params,
|
params,
|
||||||
body: body.into(),
|
body: body.into(),
|
||||||
lib: None,
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
global: None,
|
environ: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
comments: None,
|
comments: None,
|
||||||
|
@ -37,9 +37,9 @@ fn check_struct_sizes() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
size_of::<NativeCallContext>(),
|
size_of::<NativeCallContext>(),
|
||||||
if cfg!(feature = "no_position") {
|
if cfg!(feature = "no_position") {
|
||||||
64
|
|
||||||
} else {
|
|
||||||
72
|
72
|
||||||
|
} else {
|
||||||
|
80
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,11 @@ use std::{
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
use std::time::Instant;
|
pub use std::time::Instant;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
use instant::Instant;
|
pub use instant::Instant;
|
||||||
|
|
||||||
/// The message: data type was checked
|
/// The message: data type was checked
|
||||||
const CHECKED: &str = "data type was checked";
|
const CHECKED: &str = "data type was checked";
|
||||||
@ -543,74 +543,6 @@ impl Hash for Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map the name of a standard type into a friendly form.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn map_std_type_name(name: &str, shorthands: bool) -> &str {
|
|
||||||
let name = name.trim();
|
|
||||||
|
|
||||||
if name.starts_with("rhai::") {
|
|
||||||
return map_std_type_name(&name[6..], shorthands);
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == type_name::<String>() {
|
|
||||||
return if shorthands { "string" } else { "String" };
|
|
||||||
}
|
|
||||||
if name == type_name::<ImmutableString>() {
|
|
||||||
return if shorthands {
|
|
||||||
"string"
|
|
||||||
} else {
|
|
||||||
"ImmutableString"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if name == type_name::<&str>() {
|
|
||||||
return if shorthands { "string" } else { "&str" };
|
|
||||||
}
|
|
||||||
#[cfg(feature = "decimal")]
|
|
||||||
if name == type_name::<rust_decimal::Decimal>() {
|
|
||||||
return if shorthands { "decimal" } else { "Decimal" };
|
|
||||||
}
|
|
||||||
if name == type_name::<FnPtr>() {
|
|
||||||
return if shorthands { "Fn" } else { "FnPtr" };
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
if name == type_name::<crate::Array>() {
|
|
||||||
return if shorthands { "array" } else { "Array" };
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
if name == type_name::<crate::Blob>() {
|
|
||||||
return if shorthands { "blob" } else { "Blob" };
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
if name == type_name::<crate::Map>() {
|
|
||||||
return if shorthands { "map" } else { "Map" };
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
|
||||||
if name == type_name::<Instant>() {
|
|
||||||
return if shorthands { "timestamp" } else { "Instant" };
|
|
||||||
}
|
|
||||||
if name == type_name::<ExclusiveRange>() || name == "ExclusiveRange" {
|
|
||||||
return if shorthands {
|
|
||||||
"range"
|
|
||||||
} else if cfg!(feature = "only_i32") {
|
|
||||||
"Range<i32>"
|
|
||||||
} else {
|
|
||||||
"Range<i64>"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if name == type_name::<InclusiveRange>() || name == "InclusiveRange" {
|
|
||||||
return if shorthands {
|
|
||||||
"range="
|
|
||||||
} else if cfg!(feature = "only_i32") {
|
|
||||||
"RangeInclusive<i32>"
|
|
||||||
} else {
|
|
||||||
"RangeInclusive<i64>"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
name
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Dynamic {
|
impl fmt::Display for Dynamic {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
|
@ -30,6 +30,8 @@ pub enum EvalAltResult {
|
|||||||
/// Syntax error.
|
/// Syntax error.
|
||||||
ErrorParsing(ParseErrorType, Position),
|
ErrorParsing(ParseErrorType, Position),
|
||||||
|
|
||||||
|
/// Shadowing of an existing variable disallowed. Wrapped value is the variable name.
|
||||||
|
ErrorVariableExists(String, Position),
|
||||||
/// Usage of an unknown variable. Wrapped value is the variable name.
|
/// Usage of an unknown variable. Wrapped value is the variable name.
|
||||||
ErrorVariableNotFound(String, Position),
|
ErrorVariableNotFound(String, Position),
|
||||||
/// Call to an unknown function. Wrapped value is the function signature.
|
/// Call to an unknown function. Wrapped value is the function signature.
|
||||||
@ -139,8 +141,9 @@ impl fmt::Display for EvalAltResult {
|
|||||||
}
|
}
|
||||||
Self::ErrorInModule(s, err, _) => write!(f, "Error in module {}: {}", s, err)?,
|
Self::ErrorInModule(s, err, _) => write!(f, "Error in module {}: {}", s, err)?,
|
||||||
|
|
||||||
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
|
Self::ErrorVariableExists(s, _) => write!(f, "Variable is already defined: {}", s)?,
|
||||||
Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
|
Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
|
||||||
|
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
|
||||||
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?,
|
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?,
|
||||||
Self::ErrorDataRace(s, _) => {
|
Self::ErrorDataRace(s, _) => {
|
||||||
write!(f, "Data race detected when accessing variable: {}", s)?
|
write!(f, "Data race detected when accessing variable: {}", s)?
|
||||||
@ -149,7 +152,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
"" => f.write_str("Malformed dot expression"),
|
"" => f.write_str("Malformed dot expression"),
|
||||||
s => f.write_str(s),
|
s => f.write_str(s),
|
||||||
}?,
|
}?,
|
||||||
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for {}", s)?,
|
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered: {}", s)?,
|
||||||
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
|
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
|
||||||
Self::ErrorFor(_) => f.write_str("For loop expects a type that is iterable")?,
|
Self::ErrorFor(_) => f.write_str("For loop expects a type that is iterable")?,
|
||||||
Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
|
Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
|
||||||
@ -166,7 +169,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
}
|
}
|
||||||
Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?,
|
Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?,
|
||||||
|
|
||||||
Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant {}", s)?,
|
Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant: {}", s)?,
|
||||||
Self::ErrorMismatchOutputType(s, r, _) => match (r.as_str(), s.as_str()) {
|
Self::ErrorMismatchOutputType(s, r, _) => match (r.as_str(), s.as_str()) {
|
||||||
("", s) => write!(f, "Output type is incorrect, expecting {}", s),
|
("", s) => write!(f, "Output type is incorrect, expecting {}", s),
|
||||||
(r, "") => write!(f, "Output type is incorrect: {}", r),
|
(r, "") => write!(f, "Output type is incorrect: {}", r),
|
||||||
@ -274,6 +277,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorBitFieldBounds(_, _, _)
|
| Self::ErrorBitFieldBounds(_, _, _)
|
||||||
| Self::ErrorIndexingType(_, _)
|
| Self::ErrorIndexingType(_, _)
|
||||||
| Self::ErrorFor(_)
|
| Self::ErrorFor(_)
|
||||||
|
| Self::ErrorVariableExists(_, _)
|
||||||
| Self::ErrorVariableNotFound(_, _)
|
| Self::ErrorVariableNotFound(_, _)
|
||||||
| Self::ErrorModuleNotFound(_, _)
|
| Self::ErrorModuleNotFound(_, _)
|
||||||
| Self::ErrorDataRace(_, _)
|
| Self::ErrorDataRace(_, _)
|
||||||
@ -364,7 +368,8 @@ impl EvalAltResult {
|
|||||||
Self::ErrorIndexingType(t, _) => {
|
Self::ErrorIndexingType(t, _) => {
|
||||||
map.insert("type".into(), t.into());
|
map.insert("type".into(), t.into());
|
||||||
}
|
}
|
||||||
Self::ErrorVariableNotFound(v, _)
|
Self::ErrorVariableExists(v, _)
|
||||||
|
| Self::ErrorVariableNotFound(v, _)
|
||||||
| Self::ErrorDataRace(v, _)
|
| Self::ErrorDataRace(v, _)
|
||||||
| Self::ErrorAssignmentToConstant(v, _) => {
|
| Self::ErrorAssignmentToConstant(v, _) => {
|
||||||
map.insert("variable".into(), v.into());
|
map.insert("variable".into(), v.into());
|
||||||
@ -415,6 +420,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorBitFieldBounds(_, _, pos)
|
| Self::ErrorBitFieldBounds(_, _, pos)
|
||||||
| Self::ErrorIndexingType(_, pos)
|
| Self::ErrorIndexingType(_, pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
|
| Self::ErrorVariableExists(_, pos)
|
||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
| Self::ErrorModuleNotFound(_, pos)
|
| Self::ErrorModuleNotFound(_, pos)
|
||||||
| Self::ErrorDataRace(_, pos)
|
| Self::ErrorDataRace(_, pos)
|
||||||
@ -463,6 +469,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorBitFieldBounds(_, _, pos)
|
| Self::ErrorBitFieldBounds(_, _, pos)
|
||||||
| Self::ErrorIndexingType(_, pos)
|
| Self::ErrorIndexingType(_, pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
|
| Self::ErrorVariableExists(_, pos)
|
||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
| Self::ErrorModuleNotFound(_, pos)
|
| Self::ErrorModuleNotFound(_, pos)
|
||||||
| Self::ErrorDataRace(_, pos)
|
| Self::ErrorDataRace(_, pos)
|
||||||
|
@ -9,6 +9,8 @@ pub mod parse_error;
|
|||||||
pub mod scope;
|
pub mod scope;
|
||||||
|
|
||||||
pub use dynamic::Dynamic;
|
pub use dynamic::Dynamic;
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
pub use dynamic::Instant;
|
||||||
pub use error::EvalAltResult;
|
pub use error::EvalAltResult;
|
||||||
pub use fn_ptr::FnPtr;
|
pub use fn_ptr::FnPtr;
|
||||||
pub use immutable_string::ImmutableString;
|
pub use immutable_string::ImmutableString;
|
||||||
|
@ -167,6 +167,10 @@ pub enum ParseErrorType {
|
|||||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||||
/// Wrapped value is the error message (if any).
|
/// Wrapped value is the error message (if any).
|
||||||
AssignmentToInvalidLHS(String),
|
AssignmentToInvalidLHS(String),
|
||||||
|
/// A variable is already defined.
|
||||||
|
///
|
||||||
|
/// Only appears when variables shadowing is disabled.
|
||||||
|
VariableExists(String),
|
||||||
/// A variable is not found.
|
/// A variable is not found.
|
||||||
///
|
///
|
||||||
/// Only appears when strict variables mode is enabled.
|
/// Only appears when strict variables mode is enabled.
|
||||||
@ -241,6 +245,7 @@ impl fmt::Display for ParseErrorType {
|
|||||||
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
|
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
|
||||||
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s),
|
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s),
|
||||||
|
|
||||||
|
Self::VariableExists(s) => write!(f, "Variable already defined: {}", s),
|
||||||
Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s),
|
Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s),
|
||||||
Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s),
|
Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s),
|
||||||
|
|
||||||
|
@ -9,7 +9,12 @@ use rhai::Map;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.register_debugger(
|
||||||
|
|| Dynamic::UNIT,
|
||||||
|
|_, _, _, _, _| Ok(rhai::debugger::DebuggerCommand::Continue),
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -18,7 +23,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"
|
"
|
||||||
fn foo(x) {
|
fn foo(x) {
|
||||||
if x >= 5 {
|
if x >= 5 {
|
||||||
stack_trace()
|
back_trace()
|
||||||
} else {
|
} else {
|
||||||
foo(x+1)
|
foo(x+1)
|
||||||
}
|
}
|
||||||
@ -30,7 +35,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(r.len(), 6);
|
assert_eq!(r.len(), 6);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("len(stack_trace())")?, 0);
|
assert_eq!(engine.eval::<INT>("len(back_trace())")?, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -41,7 +46,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
|||||||
fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
|
fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.on_debugger(
|
engine.register_debugger(
|
||||||
|| {
|
|| {
|
||||||
// Say, use an object map for the debugger state
|
// Say, use an object map for the debugger state
|
||||||
let mut state = Map::new();
|
let mut state = Map::new();
|
||||||
@ -50,7 +55,7 @@ fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
|
|||||||
state.insert("foo".into(), false.into());
|
state.insert("foo".into(), false.into());
|
||||||
Dynamic::from_map(state)
|
Dynamic::from_map(state)
|
||||||
},
|
},
|
||||||
|context, _, _, _| {
|
|context, _, _, _, _| {
|
||||||
// Get global runtime state
|
// Get global runtime state
|
||||||
let global = context.global_runtime_state_mut();
|
let global = context.global_runtime_state_mut();
|
||||||
|
|
||||||
|
@ -514,3 +514,47 @@ fn test_module_file() -> Result<(), Box<EvalAltResult>> {
|
|||||||
Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[test]
|
||||||
|
fn test_module_environ() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let ast = engine.compile(
|
||||||
|
r#"
|
||||||
|
const SECRET = 42;
|
||||||
|
|
||||||
|
fn foo(x) {
|
||||||
|
print(global::SECRET);
|
||||||
|
global::SECRET + x
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut m = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||||
|
|
||||||
|
m.set_id("test");
|
||||||
|
m.build_index();
|
||||||
|
|
||||||
|
engine.register_static_module("test", m.into());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(
|
||||||
|
r#"
|
||||||
|
const SECRET = "hello";
|
||||||
|
|
||||||
|
fn foo(x) {
|
||||||
|
print(global::SECRET);
|
||||||
|
global::SECRET + x
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = test::foo(0);
|
||||||
|
|
||||||
|
foo(t)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
"hello42"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -78,33 +78,24 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(123 @ 1:53)] }");
|
||||||
format!("{:?}", ast),
|
|
||||||
"AST { source: \"\", body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }"
|
|
||||||
);
|
|
||||||
|
|
||||||
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{:?}", ast),
|
format!("{:?}", ast),
|
||||||
r#"AST { source: "", body: Block[Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"#
|
r#"AST { body: [Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(format!("{:?}", ast), "AST { body: [] }");
|
||||||
format!("{:?}", ast),
|
|
||||||
"AST { source: \"\", body: Block[], functions: Module, resolver: None }"
|
|
||||||
);
|
|
||||||
|
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let ast = engine.compile("abs(-42)")?;
|
let ast = engine.compile("abs(-42)")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }");
|
||||||
format!("{:?}", ast),
|
|
||||||
"AST { source: \"\", body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
|
fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -31,6 +31,20 @@ fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert!(engine.compile("while x > y { foo(z); }").is_err());
|
assert!(engine.compile("while x > y { foo(z); }").is_err());
|
||||||
|
|
||||||
|
engine.compile("let x = 42; let x = 123;")?;
|
||||||
|
|
||||||
|
engine.set_allow_shadowing(false);
|
||||||
|
|
||||||
|
assert!(engine.compile("let x = 42; let x = 123;").is_err());
|
||||||
|
assert!(engine.compile("const x = 42; let x = 123;").is_err());
|
||||||
|
assert!(engine.compile("let x = 42; const x = 123;").is_err());
|
||||||
|
assert!(engine.compile("const x = 42; const x = 123;").is_err());
|
||||||
|
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push("x", 42 as INT);
|
||||||
|
|
||||||
|
assert!(engine.run_with_scope(&mut scope, "let x = 42;").is_err());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,3 +120,26 @@ fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.on_def_var(|name, _, scope_level, _, _| match (name, scope_level) {
|
||||||
|
("x", 0 | 1) => Ok(false),
|
||||||
|
_ => Ok(true),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("let y = 42; let y = 123; let z = y + 1; z")?,
|
||||||
|
124
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(engine.run("let x = 42;").is_err());
|
||||||
|
assert!(engine.run("const x = 42;").is_err());
|
||||||
|
assert!(engine.run("let y = 42; { let x = y + 1; }").is_err());
|
||||||
|
assert!(engine.run("let y = 42; { let x = y + 1; }").is_err());
|
||||||
|
engine.run("let y = 42; { let z = y + 1; { let x = z + 1; } }")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user