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

@@ -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) => {
// foo:bar::baz::VARIABLE
if let Some(module) = self.search_imports(global, state, namespace) {
// foo:bar::baz::VARIABLE
return match module.get_qualified_var(*hash_var) {
Ok(target) => {
let mut target = target.clone();
@@ -89,18 +89,16 @@ 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));
}
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(

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())