Refactor and add state to debugger.

This commit is contained in:
Stephen Chung 2022-01-28 18:59:18 +08:00
parent 20baae71d4
commit 66af69aaff
30 changed files with 693 additions and 624 deletions

View File

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

View File

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

View File

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

View File

@ -154,10 +154,7 @@ impl Engine {
arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult {
let state = &mut EvalState::new();
let global = &mut GlobalRuntimeState::new();
#[cfg(feature = "debugging")]
global.debugger.activate(self.debugger.is_some());
let global = &mut GlobalRuntimeState::new(self);
let statements = ast.statements();

View File

@ -184,10 +184,7 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> RhaiResultOf<T> {
let global = &mut GlobalRuntimeState::new();
#[cfg(feature = "debugging")]
global.debugger.activate(self.debugger.is_some());
let global = &mut GlobalRuntimeState::new(self);
let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?;

View File

@ -260,12 +260,13 @@ impl Engine {
self.debug = Some(Box::new(callback));
self
}
/// _(debugging)_ Register a callback for debugging.
/// _(debugging)_ Register callbacks for debugging.
/// Exported under the `debugging` feature only.
#[cfg(feature = "debugging")]
#[inline(always)]
pub fn on_debugger(
&mut self,
init: impl Fn() -> Dynamic + SendSync + 'static,
callback: impl Fn(
&mut EvalContext,
crate::ast::ASTNode,
@ -275,7 +276,7 @@ impl Engine {
+ SendSync
+ 'static,
) -> &mut Self {
self.debugger = Some(Box::new(callback));
self.debugger = Some((Box::new(init), Box::new(callback)));
self
}
}

View File

@ -1037,9 +1037,9 @@ impl Engine {
signatures.extend(self.global_namespace().gen_fn_signatures());
self.global_sub_modules.iter().for_each(|(name, m)| {
for (name, m) in &self.global_sub_modules {
signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f)))
});
}
signatures.extend(
self.global_modules

View File

@ -45,9 +45,7 @@ impl Engine {
#[inline]
pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> {
let state = &mut EvalState::new();
let global = &mut GlobalRuntimeState::new();
#[cfg(feature = "debugging")]
global.debugger.activate(self.debugger.is_some());
let global = &mut GlobalRuntimeState::new(self);
global.source = ast.source_raw().clone();
#[cfg(not(feature = "no_module"))]

View File

@ -1,5 +1,5 @@
#[cfg(feature = "debugging")]
use rhai::{Dynamic, Engine, EvalAltResult, Position, Scope};
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
#[cfg(feature = "debugging")]
use rhai::debugger::DebuggerCommand;
@ -121,10 +121,7 @@ fn print_scope(scope: &Scope, dedup: bool) {
scope
};
scope
.iter_raw()
.enumerate()
.for_each(|(i, (name, constant, value))| {
for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
#[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
@ -148,7 +145,7 @@ fn print_scope(scope: &Scope, dedup: bool) {
*value.read_lock::<Dynamic>().unwrap(),
);
}
});
}
println!();
}
@ -233,33 +230,35 @@ fn main() {
// Hook up debugger
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
#[cfg(not(feature = "sync"))]
let current_source = std::cell::RefCell::new(rhai::Identifier::new_const());
#[cfg(feature = "sync")]
let current_source = std::sync::RwLock::new(rhai::Identifier::new_const());
engine.on_debugger(
// Store the current source in the debugger state
|| "".into(),
// Main debugging interface
move |context, node, source, pos| {
{
let current_source = &mut *context
.global_runtime_state_mut()
.debugger
.state_mut()
.write_lock::<ImmutableString>()
.unwrap();
engine.on_debugger(move |context, node, source, pos| {
#[cfg(not(feature = "sync"))]
let current_source = &mut *current_source.borrow_mut();
#[cfg(feature = "sync")]
let current_source = &mut *current_source.write().unwrap();
let src = source.unwrap_or("");
// Check source
if let Some(src) = source {
if src != current_source {
println!(">>> Source => {}", src);
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 {
// The root file has no source
if !current_source.is_empty() {
println!(">>> Source => main script.");
}
// Print the current source line
print_source(&lines, pos, 0);
}
*current_source = source.unwrap_or("").into();
}
// Read stdin for commands
let mut input = String::new();
@ -305,30 +304,32 @@ fn main() {
}
["print", ..] => print_scope(context.scope(), true),
["imports", ..] => {
context
for (i, (name, module)) in context
.global_runtime_state()
.scan_imports_raw()
.enumerate()
.for_each(|(i, (name, module))| {
{
println!(
"[{}] {} = {}",
i + 1,
name,
module.id().unwrap_or("<unknown>")
);
});
}
println!();
}
#[cfg(not(feature = "no_function"))]
["backtrace", ..] => {
context
for frame in context
.global_runtime_state()
.debugger
.call_stack()
.iter()
.rev()
.for_each(|frame| println!("{}", frame));
{
println!("{}", frame)
}
}
["clear", ..] => {
context
@ -338,13 +339,14 @@ fn main() {
.clear();
println!("All break-points cleared.");
}
["breakpoints", ..] => context
["breakpoints", ..] => Iterator::for_each(
context
.global_runtime_state()
.debugger
.break_points()
.iter()
.enumerate()
.for_each(|(i, bp)| match bp {
.enumerate(),
|(i, bp)| match bp {
#[cfg(not(feature = "no_position"))]
rhai::debugger::BreakPoint::AtPosition { pos, .. } => {
let line_num = format!("[{}] line ", i + 1);
@ -352,7 +354,8 @@ fn main() {
print_source(&lines, *pos, line_num.len());
}
_ => println!("[{}] {}", i + 1, bp),
}),
},
),
["enable", n, ..] => {
if let Ok(n) = n.parse::<usize>() {
let range = 1..=context
@ -504,7 +507,9 @@ fn main() {
.break_points_mut()
.push(bp);
}
["throw"] => break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()),
["throw"] => {
break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into())
}
["throw", _msg, ..] => {
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
@ -518,7 +523,8 @@ fn main() {
Err(err) => panic!("input error: {}", err),
}
}
});
},
);
// Set a file module resolver without caching
#[cfg(not(feature = "no_module"))]

View File

@ -61,10 +61,7 @@ fn print_help() {
/// Display the scope.
fn print_scope(scope: &Scope) {
scope
.iter_raw()
.enumerate()
.for_each(|(i, (name, constant, value))| {
for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
#[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
@ -78,7 +75,7 @@ fn print_scope(scope: &Scope) {
value_is_shared,
*value.read_lock::<Dynamic>().unwrap(),
)
});
}
println!();
}
@ -282,13 +279,13 @@ fn main() {
#[cfg(feature = "metadata")]
"functions" => {
// print a list of all registered functions
engine
.gen_fn_signatures(false)
.into_iter()
.for_each(|f| println!("{}", f));
for f in engine.gen_fn_signatures(false) {
println!("{}", f)
}
#[cfg(not(feature = "no_function"))]
main_ast.iter_functions().for_each(|f| println!("{}", f));
for f in main_ast.iter_functions() {
println!("{}", f)
}
println!();
continue;

View File

@ -140,7 +140,10 @@ pub struct Engine {
/// Callback closure for debugging.
#[cfg(feature = "debugging")]
pub(crate) debugger: Option<Box<crate::eval::OnDebuggerCallback>>,
pub(crate) debugger: Option<(
Box<crate::eval::OnDebuggingInit>,
Box<crate::eval::OnDebuggerCallback>,
)>,
}
impl fmt::Debug for Engine {

View File

@ -8,11 +8,18 @@ use std::fmt;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// A standard callback function for debugging.
/// Callback function to initialize the debugger.
#[cfg(not(feature = "sync"))]
pub type OnDebuggingInit = dyn Fn() -> Dynamic;
/// Callback function to initialize the debugger.
#[cfg(feature = "sync")]
pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
/// Callback function for debugging.
#[cfg(not(feature = "sync"))]
pub type OnDebuggerCallback =
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>;
/// A standard callback function for debugging.
/// Callback function for debugging.
#[cfg(feature = "sync")]
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
+ Send
@ -192,6 +199,8 @@ impl fmt::Display for CallStackFrame {
pub struct Debugger {
/// The current status command.
status: DebuggerCommand,
/// The current state.
state: Dynamic,
/// The current set of break-points.
break_points: Vec<BreakPoint>,
/// The current function call stack.
@ -200,28 +209,44 @@ pub struct Debugger {
}
impl Debugger {
/// Create a new [`Debugger`].
pub const fn new() -> Self {
/// Create a new [`Debugger`] based on an [`Engine`].
#[inline(always)]
#[must_use]
pub fn new(engine: &Engine) -> Self {
Self {
status: DebuggerCommand::Continue,
status: if engine.debugger.is_some() {
DebuggerCommand::StepInto
} else {
DebuggerCommand::Continue
},
state: if let Some((ref init, _)) = engine.debugger {
init()
} else {
Dynamic::UNIT
},
break_points: Vec::new(),
#[cfg(not(feature = "no_function"))]
call_stack: Vec::new(),
}
}
/// Get the function call stack depth.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
/// Get a reference to the current state.
#[inline(always)]
pub fn call_stack_len(&self) -> usize {
self.call_stack.len()
#[must_use]
pub fn state(&self) -> &Dynamic {
&self.state
}
/// Get a mutable reference to the current state.
#[inline(always)]
#[must_use]
pub fn state_mut(&mut self) -> &mut Dynamic {
&mut self.state
}
/// Get the current call stack.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn call_stack(&self) -> &[CallStackFrame] {
&self.call_stack
}
@ -258,10 +283,11 @@ impl Debugger {
pub fn status(&self) -> DebuggerCommand {
self.status
}
/// Set the status of this [`Debugger`].
/// Get a mutable reference to the current status of this [`Debugger`].
#[inline(always)]
pub fn set_status(&mut self, status: DebuggerCommand) {
self.status = status;
#[must_use]
pub fn status_mut(&mut self) -> &mut DebuggerCommand {
&mut self.status
}
/// Set the status of this [`Debugger`].
#[inline(always)]
@ -270,17 +296,8 @@ impl Debugger {
self.status = cmd;
}
}
/// Activate: set the status of this [`Debugger`] to [`DebuggerCommand::StepInto`].
/// Deactivate: set the status of this [`Debugger`] to [`DebuggerCommand::Continue`].
#[inline(always)]
pub fn activate(&mut self, active: bool) {
if active {
self.set_status(DebuggerCommand::StepInto);
} else {
self.set_status(DebuggerCommand::Continue);
}
}
/// Does a particular [`AST` Node][ASTNode] trigger a break-point?
#[must_use]
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool {
let _src = src;
@ -347,7 +364,7 @@ impl Engine {
if let Some(cmd) =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)?
{
global.debugger.set_status(cmd);
global.debugger.status = cmd;
}
Ok(())
@ -375,7 +392,7 @@ impl Engine {
node: impl Into<ASTNode<'a>>,
level: usize,
) -> RhaiResultOf<Option<DebuggerCommand>> {
if let Some(ref on_debugger) = self.debugger {
if let Some((_, ref on_debugger)) = self.debugger {
let node = node.into();
// Skip transitive nodes
@ -415,19 +432,19 @@ impl Engine {
match command {
DebuggerCommand::Continue => {
global.debugger.set_status(DebuggerCommand::Continue);
global.debugger.status = DebuggerCommand::Continue;
Ok(None)
}
DebuggerCommand::Next => {
global.debugger.set_status(DebuggerCommand::Continue);
global.debugger.status = DebuggerCommand::Continue;
Ok(Some(DebuggerCommand::Next))
}
DebuggerCommand::StepInto => {
global.debugger.set_status(DebuggerCommand::StepInto);
global.debugger.status = DebuggerCommand::StepInto;
Ok(None)
}
DebuggerCommand::StepOver => {
global.debugger.set_status(DebuggerCommand::Continue);
global.debugger.status = DebuggerCommand::Continue;
Ok(Some(DebuggerCommand::StepOver))
}
}

View File

@ -64,8 +64,8 @@ impl Engine {
// Qualified variable access
#[cfg(not(feature = "no_module"))]
(_, Some((namespace, hash_var)), var_name) => {
if let Some(module) = self.search_imports(global, state, namespace) {
// foo:bar::baz::VARIABLE
if let Some(module) = self.search_imports(global, state, namespace) {
return match module.get_qualified_var(*hash_var) {
Ok(target) => {
let mut target = target.clone();
@ -89,19 +89,17 @@ impl Engine {
};
}
// global::VARIABLE
#[cfg(not(feature = "no_function"))]
if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL {
// global::VARIABLE
let global_constants = global.constants_mut();
let mut guard = crate::func::native::locked_write(&global.constants);
if let Some(mut guard) = global_constants {
if let Some(value) = guard.get_mut(var_name) {
let mut target: Target = value.clone().into();
// Module variables are constant
target.set_access_mode(AccessMode::ReadOnly);
return Ok((target.into(), *_var_pos));
}
}
return Err(ERR::ErrorVariableNotFound(
format!(

View File

@ -1,13 +1,13 @@
//! Global runtime state.
use crate::func::{CallableFunction, IteratorFn};
use crate::{Identifier, Module, Shared, StaticVec};
use crate::{Engine, Identifier, Module, Shared, StaticVec};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
any::TypeId,
fmt,
iter::{FromIterator, Rev, Zip},
iter::{Rev, Zip},
marker::PhantomData,
};
@ -44,8 +44,7 @@ pub struct GlobalRuntimeState<'a> {
/// Cache of globally-defined constants.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
constants:
Option<Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>>,
pub(crate) constants: crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>,
/// Debugging interface.
#[cfg(feature = "debugging")]
pub debugger: super::Debugger,
@ -53,18 +52,11 @@ pub struct GlobalRuntimeState<'a> {
dummy: PhantomData<&'a ()>,
}
impl Default for GlobalRuntimeState<'_> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl GlobalRuntimeState<'_> {
/// Create a new [`GlobalRuntimeState`].
/// Create a new [`GlobalRuntimeState`] based on an [`Engine`].
#[inline(always)]
#[must_use]
pub fn new() -> Self {
pub fn new(engine: &Engine) -> Self {
Self {
keys: StaticVec::new_const(),
modules: StaticVec::new_const(),
@ -77,9 +69,9 @@ impl GlobalRuntimeState<'_> {
fn_hash_indexing: (0, 0),
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
constants: None,
constants: std::collections::BTreeMap::new().into(),
#[cfg(feature = "debugging")]
debugger: crate::eval::Debugger::new(),
debugger: crate::eval::Debugger::new(engine),
dummy: PhantomData::default(),
}
}
@ -193,33 +185,6 @@ impl GlobalRuntimeState<'_> {
s => Some(s),
}
}
/// Get a mutable reference to the cache of globally-defined constants.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
#[must_use]
pub(crate) fn constants_mut<'a>(
&'a mut self,
) -> Option<
impl std::ops::DerefMut<Target = std::collections::BTreeMap<Identifier, crate::Dynamic>> + 'a,
> {
if let Some(ref global_constants) = self.constants {
Some(crate::func::native::shared_write_lock(global_constants))
} else {
None
}
}
/// Set a constant into the cache of globally-defined constants.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub(crate) fn set_constant(&mut self, name: impl Into<Identifier>, value: crate::Dynamic) {
if self.constants.is_none() {
let dict: crate::Locked<_> = std::collections::BTreeMap::new().into();
self.constants = Some(dict.into());
}
crate::func::native::shared_write_lock(self.constants.as_mut().expect("`Some`"))
.insert(name.into(), value);
}
/// Get the pre-calculated index getter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[must_use]
@ -262,22 +227,13 @@ impl IntoIterator for GlobalRuntimeState<'_> {
}
}
impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for GlobalRuntimeState<'_> {
#[inline]
fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self {
let mut lib = Self::new();
lib.extend(iter);
lib
}
}
impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRuntimeState<'_> {
#[inline]
fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(k, m)| {
for (k, m) in iter {
self.keys.push(k.into());
self.modules.push(m.into());
})
}
}
}

View File

@ -14,7 +14,7 @@ pub use chaining::{ChainArgument, ChainType};
#[cfg(not(feature = "no_function"))]
pub use debugger::CallStackFrame;
#[cfg(feature = "debugging")]
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback};
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback, OnDebuggingInit};
pub use eval_context::EvalContext;
pub use eval_state::EvalState;
pub use global_state::GlobalRuntimeState;

View File

@ -818,8 +818,8 @@ impl Engine {
&& entry_type == AccessMode::ReadOnly
&& lib.iter().any(|&m| !m.is_empty())
{
// Add a global constant if at top level and there are functions
global.set_constant(var_name.clone(), value.clone());
crate::func::native::locked_write(&global.constants)
.insert(var_name.clone(), value.clone());
}
if export {
@ -869,23 +869,20 @@ impl Engine {
if let Ok(path) = path_result {
use crate::ModuleResolver;
let source = match global.source.as_str() {
"" => None,
s => Some(s),
};
let path_pos = expr.position();
let module_result = global
.embedded_module_resolver
let resolver = global.embedded_module_resolver.clone();
let module_result = resolver
.as_ref()
.and_then(|r| match r.resolve(self, source, &path, path_pos) {
.and_then(|r| match r.resolve_raw(self, global, &path, path_pos) {
Err(err) if matches!(*err, ERR::ErrorModuleNotFound(_, _)) => None,
result => Some(result),
})
.or_else(|| {
self.module_resolver
.as_ref()
.map(|r| r.resolve(self, source, &path, path_pos))
.map(|r| r.resolve_raw(self, global, &path, path_pos))
})
.unwrap_or_else(|| {
Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into())

View File

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

View File

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

View File

@ -74,7 +74,7 @@ impl Engine {
#[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
scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| {
@ -115,10 +115,9 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
if let Some(ref modules) = fn_def.global {
modules
.iter()
.cloned()
.for_each(|(n, m)| global.push_import(n, m));
for (n, m) in modules.iter().cloned() {
global.push_import(n, m)
}
}
// Evaluate the function

View File

@ -1401,9 +1401,9 @@ impl Module {
/// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level.
#[inline]
pub fn combine_flatten(&mut self, other: Self) -> &mut Self {
other.modules.into_iter().for_each(|(_, m)| {
for (_, m) in other.modules.into_iter() {
self.combine_flatten(shared_take_or_clone(m));
});
}
self.variables.extend(other.variables.into_iter());
self.functions.extend(other.functions.into_iter());
self.type_iterators.extend(other.type_iterators.into_iter());
@ -1419,22 +1419,22 @@ impl Module {
/// Only items not existing in this [`Module`] are added.
#[inline]
pub fn fill_with(&mut self, other: &Self) -> &mut Self {
other.modules.iter().for_each(|(k, v)| {
for (k, v) in &other.modules {
if !self.modules.contains_key(k) {
self.modules.insert(k.clone(), v.clone());
}
});
other.variables.iter().for_each(|(k, v)| {
}
for (k, v) in &other.variables {
if !self.variables.contains_key(k) {
self.variables.insert(k.clone(), v.clone());
}
});
other.functions.iter().for_each(|(&k, v)| {
}
for (&k, v) in &other.functions {
self.functions.entry(k).or_insert_with(|| v.clone());
});
other.type_iterators.iter().for_each(|(&k, v)| {
}
for (&k, v) in &other.type_iterators {
self.type_iterators.entry(k).or_insert(v.clone());
});
}
self.all_functions.clear();
self.all_variables.clear();
self.all_type_iterators.clear();
@ -1455,12 +1455,11 @@ impl Module {
other: &Self,
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy,
) -> &mut Self {
#[cfg(not(feature = "no_function"))]
other.modules.iter().for_each(|(k, v)| {
for (k, v) in &other.modules {
let mut m = Self::new();
m.merge_filtered(v, _filter);
self.set_sub_module(k.clone(), m);
});
}
#[cfg(feature = "no_function")]
self.modules
.extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone())));
@ -1658,28 +1657,74 @@ impl Module {
/// # }
/// ```
#[cfg(not(feature = "no_module"))]
#[inline(always)]
pub fn eval_ast_as_new(
scope: crate::Scope,
ast: &crate::AST,
engine: &crate::Engine,
) -> RhaiResultOf<Self> {
let global = &mut crate::eval::GlobalRuntimeState::new(engine);
Self::eval_ast_as_new_raw(engine, scope, global, ast)
}
/// Create a new [`Module`] by evaluating an [`AST`][crate::AST].
///
/// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions
/// to cross-call each other. Functions in the global namespace, plus all functions
/// defined in the [`Module`], are _merged_ into a _unified_ namespace before each call.
/// Therefore, all functions will be found.
#[cfg(not(feature = "no_module"))]
pub(crate) fn eval_ast_as_new_raw(
engine: &crate::Engine,
scope: crate::Scope,
global: &mut crate::eval::GlobalRuntimeState,
ast: &crate::AST,
) -> RhaiResultOf<Self> {
let mut scope = scope;
let mut global = crate::eval::GlobalRuntimeState::new();
#[cfg(feature = "debugging")]
global.debugger.activate(engine.debugger.is_some());
// Save global state
let orig_imports_len = global.num_imports();
let orig_source = global.source.clone();
let orig_constants = std::mem::take(&mut global.constants);
// Run the script
engine.eval_ast_with_scope_raw(&mut scope, &mut global, &ast, 0)?;
let result = engine.eval_ast_with_scope_raw(&mut scope, global, &ast, 0);
// Create new module
let mut module =
scope
.into_iter()
.fold(Module::new(), |mut module, (_, value, mut aliases)| {
let mut module = Module::new();
// Extra modules left become sub-modules
#[cfg(not(feature = "no_function"))]
let mut func_global = None;
if result.is_ok() {
global
.scan_imports_raw()
.skip(orig_imports_len)
.for_each(|(k, m)| {
#[cfg(not(feature = "no_function"))]
if func_global.is_none() {
func_global = Some(StaticVec::new());
}
#[cfg(not(feature = "no_function"))]
func_global
.as_mut()
.expect("`Some`")
.push((k.clone(), m.clone()));
module.set_sub_module(k.clone(), m.clone());
});
}
// Restore global state
global.constants = orig_constants;
global.truncate_imports(orig_imports_len);
global.source = orig_source;
result?;
// Variables with an alias left in the scope become module variables
for (_, value, mut aliases) in scope {
match aliases.len() {
0 => (),
1 => {
@ -1688,30 +1733,14 @@ impl Module {
}
_ => {
let last_alias = aliases.pop().unwrap();
aliases.into_iter().for_each(|alias| {
for alias in aliases {
module.set_var(alias, value.clone());
});
}
// Avoid cloning the last value
module.set_var(last_alias, value);
}
}
module
});
// Extra modules left in the scope become sub-modules
#[cfg(not(feature = "no_function"))]
let mut func_global = None;
global.into_iter().skip(orig_imports_len).for_each(|kv| {
#[cfg(not(feature = "no_function"))]
if func_global.is_none() {
func_global = Some(StaticVec::new());
}
#[cfg(not(feature = "no_function"))]
func_global.as_mut().expect("`Some`").push(kv.clone());
module.set_sub_module(kv.0, kv.1);
});
#[cfg(not(feature = "no_function"))]
let func_global = func_global.map(|v| v.into_boxed_slice());
@ -1773,29 +1802,29 @@ impl Module {
) -> bool {
let mut contains_indexed_global_functions = false;
module.modules.iter().for_each(|(name, m)| {
for (name, m) in &module.modules {
// Index all the sub-modules first.
path.push(name);
if index_module(m, path, variables, functions, type_iterators) {
contains_indexed_global_functions = true;
}
path.pop();
});
}
// Index all variables
module.variables.iter().for_each(|(var_name, value)| {
for (var_name, value) in &module.variables {
let hash_var = crate::calc_qualified_var_hash(path.iter().copied(), var_name);
variables.insert(hash_var, value.clone());
});
}
// Index type iterators
module.type_iterators.iter().for_each(|(&type_id, func)| {
for (&type_id, func) in &module.type_iterators {
type_iterators.insert(type_id, func.clone());
contains_indexed_global_functions = true;
});
}
// Index all Rust functions
module.functions.iter().for_each(|(&hash, f)| {
for (&hash, f) in &module.functions {
match f.metadata.namespace {
FnNamespace::Global => {
// Flatten all functions with global namespace
@ -1806,7 +1835,7 @@ impl Module {
}
match f.metadata.access {
FnAccess::Public => (),
FnAccess::Private => return, // Do not index private functions
FnAccess::Private => continue, // Do not index private functions
}
if !f.func.is_script() {
@ -1824,7 +1853,7 @@ impl Module {
);
functions.insert(hash_qualified_script, f.func.clone());
}
});
}
contains_indexed_global_functions
}

View File

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

View File

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

View File

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

View File

@ -138,7 +138,7 @@ impl<'a> OptimizerState<'a> {
self.engine
.call_native_fn(
&mut GlobalRuntimeState::new(),
&mut GlobalRuntimeState::new(&self.engine),
&mut EvalState::new(),
lib,
fn_name,
@ -237,7 +237,7 @@ fn optimize_stmt_block(
});
// Optimize each statement in the block
statements.iter_mut().for_each(|stmt| {
for stmt in statements.iter_mut() {
match stmt {
Stmt::Var(value_expr, x, options, _) => {
if options.contains(AST_OPTION_CONSTANT) {
@ -260,7 +260,7 @@ fn optimize_stmt_block(
// Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result),
}
});
}
// Remove all pure statements except the last one
let mut index = 0;
@ -640,7 +640,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// switch
Stmt::Switch(match_expr, x, _) => {
optimize_expr(match_expr, state, false);
x.cases.values_mut().for_each(|block| {
for block in x.cases.values_mut() {
let statements = mem::take(&mut *block.statements);
*block.statements =
optimize_stmt_block(statements, state, preserve_result, true, false);
@ -652,7 +652,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
_ => block.condition = Some(condition),
}
}
});
}
// Remove false cases
while let Some((&key, _)) = x.cases.iter().find(|(_, block)| match block.condition {
@ -1159,13 +1159,13 @@ fn optimize_top_level(
);
// Add constants and variables from the scope
scope.iter().for_each(|(name, constant, value)| {
for (name, constant, value) in scope.iter() {
if !constant {
state.push_var(name, AccessMode::ReadWrite, None);
} else {
state.push_var(name, AccessMode::ReadOnly, Some(value));
}
});
}
statements = optimize_stmt_block(statements, &mut state, true, false, true);
statements
@ -1191,9 +1191,8 @@ pub fn optimize_into_ast(
// We only need the script library's signatures for optimization purposes
let mut lib2 = crate::Module::new();
functions
.iter()
.map(|fn_def| crate::ast::ScriptFnDef {
for fn_def in &functions {
lib2.set_script_fn(crate::ast::ScriptFnDef {
name: fn_def.name.clone(),
access: fn_def.access,
body: crate::ast::StmtBlock::NONE,
@ -1204,33 +1203,25 @@ pub fn optimize_into_ast(
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
comments: None,
})
.for_each(|fn_def| {
lib2.set_script_fn(fn_def);
});
}
let lib2 = &[&lib2];
functions
.into_iter()
.map(|fn_def| {
for fn_def in functions {
let mut fn_def = crate::func::native::shared_take_or_clone(fn_def);
// Optimize the function body
let body = mem::take(&mut *fn_def.body);
*fn_def.body =
optimize_top_level(body, engine, scope, lib2, optimization_level);
*fn_def.body = optimize_top_level(body, engine, scope, lib2, optimization_level);
fn_def
})
.for_each(|fn_def| {
module.set_script_fn(fn_def);
});
}
} else {
functions.into_iter().for_each(|fn_def| {
for fn_def in functions {
module.set_script_fn(fn_def);
});
}
}
module

View File

@ -189,7 +189,7 @@ fn collect_fn_metadata(
.for_each(|(_, _, _, _, f)| {
list.push(make_metadata(dict, Some(namespace.clone()), f).into())
});
module.iter_sub_modules().for_each(|(ns, m)| {
for (ns, m) in module.iter_sub_modules() {
let ns = format!(
"{}{}{}",
namespace,
@ -197,11 +197,12 @@ fn collect_fn_metadata(
ns
);
scan_module(list, dict, ns.into(), m.as_ref(), filter)
});
}
}
ctx.iter_imports_raw()
.for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter));
for (ns, m) in ctx.iter_imports_raw() {
scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)
}
}
list

View File

@ -3450,9 +3450,9 @@ impl Engine {
{
let mut m = crate::Module::new();
_lib.into_iter().for_each(|fn_def| {
for fn_def in _lib {
m.set_script_fn(fn_def);
});
}
return Ok(AST::new(statements, m));
}

View File

@ -206,9 +206,9 @@ impl Engine {
let _ast = ast;
let mut global = ModuleMetadata::new();
self.global_sub_modules.iter().for_each(|(name, m)| {
for (name, m) in &self.global_sub_modules {
global.modules.insert(name, m.as_ref().into());
});
}
self.global_modules
.iter()
@ -221,11 +221,11 @@ impl Engine {
});
#[cfg(not(feature = "no_function"))]
_ast.shared_lib().iter_fn().for_each(|f| {
for f in _ast.shared_lib().iter_fn() {
let mut meta: FnMetadata = f.into();
meta.namespace = FnNamespace::Global;
global.functions.push(meta);
});
}
global.functions.sort();

View File

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

View File

@ -611,9 +611,9 @@ impl Scope<'_> {
impl<K: Into<Identifier>> Extend<(K, Dynamic)> for Scope<'_> {
#[inline]
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, value)| {
for (name, value) in iter {
self.push_dynamic_value(name, AccessMode::ReadWrite, value);
});
}
}
}
@ -629,7 +629,7 @@ impl<K: Into<Identifier>> FromIterator<(K, Dynamic)> for Scope<'_> {
impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
#[inline]
fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, is_constant, value)| {
for (name, is_constant, value) in iter {
self.push_dynamic_value(
name,
if is_constant {
@ -639,7 +639,7 @@ impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
},
value,
);
});
}
}
}

View File

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