use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use rusty_v8 as v8;
use crate::debugger::{
breakpoint::{BreakpointManager, Breakpoint},
stack_trace::{StackTrace, StackFrame},
variable_scope::VariableInspector,
watch::{WatchManager, WatchExpression},
DebugEvent, DebugResult, DebugStats, StepType, SourceLocation,
config::DebugConfig,
v8_stubs::{DebugEvent as V8DebugEvent, DebugExecutionState},
};
#[derive(Debug, Clone, PartialEq)]
pub enum DebugState {
Running,
Paused,
Stepping,
Terminated,
}
pub struct DebuggerEngine {
config: DebugConfig,
state: Arc<Mutex<DebugState>>,
breakpoint_manager: BreakpointManager,
watch_manager: WatchManager,
current_stack: Arc<Mutex<Option<StackTrace>>>,
stats: Arc<Mutex<DebugStats>>,
current_breakpoint_id: Option<String>,
step_type: Option<StepType>,
event_listeners: Vec<Box<dyn DebugEventListener + Send + Sync>>,
}
pub trait DebugEventListener {
fn on_event(&self, event: &DebugEvent);
fn on_breakpoint_hit(&self, breakpoint: &Breakpoint);
fn on_exception(&self, exception: &str, location: &SourceLocation);
fn on_step_completed(&self, step_type: &StepType, location: &SourceLocation);
}
pub struct SimpleEventListener {
pub events: Arc<Mutex<Vec<DebugEvent>>>,
}
impl SimpleEventListener {
pub fn new() -> Self {
Self {
events: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn get_events(&self) -> Vec<DebugEvent> {
let events: _ = self.events.lock().unwrap();
events.clone()
}
}
impl DebugEventListener for SimpleEventListener {
fn on_event(&self, event: &DebugEvent) {
let mut events = self.events.lock().unwrap();
events.push(event.clone());
}
fn on_breakpoint_hit(&self, breakpoint: &Breakpoint) {
println!("Breakpoint hit: {} at {}:{}", breakpoint.id, breakpoint.script_name, breakpoint.line_number);
}
fn on_exception(&self, exception: &str, location: &SourceLocation) {
println!("Exception: {} at {}:{}", exception, location.script_name, location.line_number);
}
fn on_step_completed(&self, step_type: &StepType, location: &SourceLocation) {
println!("Step {:?} at {}:{}", step_type, location.script_name, location.line_number);
}
}
impl DebuggerEngine {
pub fn new(config: DebugConfig) -> Self {
Self {
config,
state: Arc::new(Mutex::new(DebugState::Running)),
breakpoint_manager: BreakpointManager::new(),
watch_manager: WatchManager::new(),
current_stack: Arc::new(Mutex::new(None)),
stats: Arc::new(Mutex::new(DebugStats::new())),
current_breakpoint_id: None,
step_type: None,
event_listeners: Vec::new(),
}
}
pub fn new_default() -> Self {
Self::new(DebugConfig::default())
}
pub fn initialize(&self, _isolate: &mut v8::Isolate) -> DebugResult<()> {
DebugResult::ok(())
}
pub fn set_breakpoint(
&mut self,
script_id: String,
script_name: String,
line_number: u32,
) -> DebugResult<Breakpoint> {
let result: _ = self.breakpoint_manager.add(script_id, script_name, line_number, 0);
if result.success {
if let Some(breakpoint) = &result.data {
let mut stats = self.stats.lock().unwrap();
stats.breakpoints_set += 1;
}
result
} else {
DebugResult::err(result.error.unwrap_or_else(|| "Unknown error".to_string()))
}
}
pub fn set_conditional_breakpoint(
&mut self,
script_id: String,
script_name: String,
line_number: u32,
condition: crate::debugger::BreakpointCondition,
) -> DebugResult<Breakpoint> {
let result: _ = self.breakpoint_manager.add_conditional(
script_id,
script_name,
line_number,
0,
condition,
);
if result.success {
if let Some(breakpoint) = &result.data {
let mut stats = self.stats.lock().unwrap();
stats.breakpoints_set += 1;
}
result
} else {
DebugResult::err(result.error.unwrap_or_else(|| "Unknown error".to_string()))
}
}
pub fn remove_breakpoint(&mut self, id: &str) -> DebugResult<()> {
self.breakpoint_manager.remove_breakpoint(id)
}
pub fn enable_breakpoint(&mut self, id: &str) -> DebugResult<()> {
self.breakpoint_manager.enable_breakpoint(id)
}
pub fn disable_breakpoint(&mut self, id: &str) -> DebugResult<()> {
self.breakpoint_manager.disable_breakpoint(id)
}
pub fn continue_execution(&self) -> DebugResult<()> {
let mut state = self.state.lock().unwrap();
*state = DebugState::Running;
DebugResult::ok(())
}
pub fn step_over(&mut self) -> DebugResult<()> {
let mut state = self.state.lock().unwrap();
*state = DebugState::Stepping;
self.step_type = Some(StepType::Over);
DebugResult::ok(())
}
pub fn step_into(&mut self) -> DebugResult<()> {
let mut state = self.state.lock().unwrap();
*state = DebugState::Stepping;
self.step_type = Some(StepType::Into);
DebugResult::ok(())
}
pub fn step_out(&mut self) -> DebugResult<()> {
let mut state = self.state.lock().unwrap();
*state = DebugState::Stepping;
self.step_type = Some(StepType::Out);
DebugResult::ok(())
}
pub fn next(&mut self) -> DebugResult<()> {
let mut state = self.state.lock().unwrap();
*state = DebugState::Stepping;
self.step_type = Some(StepType::Next);
DebugResult::ok(())
}
pub fn pause(&self) -> DebugResult<()> {
let mut state = self.state.lock().unwrap();
*state = DebugState::Paused;
DebugResult::ok(())
}
pub fn terminate(&self) -> DebugResult<()> {
let mut state = self.state.lock().unwrap();
*state = DebugState::Terminated;
DebugResult::ok(())
}
pub fn get_state(&self) -> DebugState {
let state: _ = self.state.lock().unwrap();
state.clone()
}
pub fn get_stack_trace(&self) -> Option<StackTrace> {
let stack: _ = self.current_stack.lock().unwrap();
stack.clone()
}
pub fn get_stack_frames(&self) -> Option<Vec<StackFrame>> {
let stack: _ = self.current_stack.lock().unwrap();
stack.as_ref().map(|s| s.frames.clone())
}
pub fn should_pause(&self, script_id: &str, line_number: u32) -> bool {
let breakpoints: _ = self.breakpoint_manager.find_breakpoints(script_id, line_number);
if !breakpoints.is_empty() {
let mut should_pause = false;
for bp in breakpoints {
if bp.should_trigger() {
should_pause = true;
}
}
if should_pause {
let mut state = self.state.lock().unwrap();
*state = DebugState::Paused;
{
let mut stats = self.stats.lock().unwrap();
stats.breakpoints_hit += 1;
}
}
return should_pause;
}
let state: _ = self.state.lock().unwrap();
matches!(*state, DebugState::Stepping)
}
pub fn handle_debug_event(
&self,
event: V8DebugEvent,
exec_state: &v8::Global<DebugExecutionState>,
) {
match event {
V8DebugEvent::Break => {
let location: _ = self.extract_location(exec_state);
if let Some(loc) = location {
let breakpoints: _ = self.breakpoint_manager.find_breakpoints(&loc.script_id, loc.line_number);
if !breakpoints.is_empty() {
for listener in &self.event_listeners {
listener.on_breakpoint_hit(breakpoints[0]);
}
}
let debug_event: _ = DebugEvent::BreakpointHit {
breakpoint_id: self.current_breakpoint_id.clone().unwrap_or_default(),
location: loc.clone(),
};
self.notify_listeners(&debug_event);
}
}
V8DebugEvent::Exception => {
let location: _ = self.extract_location(exec_state);
if let Some(loc) = location {
let debug_event: _ = DebugEvent::Exception {
exception: "Uncaught exception".to_string(),
location: loc,
};
self.notify_listeners(&debug_event);
}
}
V8DebugEvent::CompileError => {
}
V8DebugEvent::CompileProgram => {
}
_ => {
}
}
}
fn extract_location(&self, exec_state: &v8::Global<DebugExecutionState>) -> Option<SourceLocation> {
Some(SourceLocation {
script_id: "unknown".to_string(),
script_name: "unknown.js".to_string(),
line_number: 0,
column_number: 0,
})
}
pub fn update_stack_trace(&self, stack_trace: StackTrace) {
let mut current_stack = self.current_stack.lock().unwrap();
*current_stack = Some(stack_trace);
}
pub fn get_all_breakpoints(&self) -> Vec<Breakpoint> {
self.breakpoint_manager.get_all_breakpoints().into_iter().cloned().collect()
}
pub fn get_enabled_breakpoints(&self) -> Vec<Breakpoint> {
self.breakpoint_manager.get_enabled_breakpoints().into_iter().cloned().collect()
}
pub fn get_stats(&self) -> DebugStats {
let stats: _ = self.stats.lock().unwrap();
stats.clone()
}
pub fn add_event_listener(&mut self, listener: Box<dyn DebugEventListener + Send + Sync>) {
self.event_listeners.push(listener);
}
pub fn clear_event_listeners(&mut self) {
self.event_listeners.clear();
}
fn notify_listeners(&self, event: &DebugEvent) {
for listener in &self.event_listeners {
listener.on_event(event);
}
}
pub fn evaluate_expression(
&self,
context: &v8::Global<v8::Context>,
expression: &str,
) -> DebugResult<String> {
let inspector: _ = VariableInspector::new(self.config.clone());
let result: _ = inspector.evaluate_expression(context, expression);
if result.success {
if let Some(var_info) = result.data {
DebugResult::ok(var_info.value)
} else {
DebugResult::err("No data returned".to_string())
}
} else {
DebugResult::err(result.error.unwrap_or_else(|| "Unknown error".to_string()))
}
}
pub fn get_current_variables(
&self,
context: &v8::Global<v8::Context>,
) -> DebugResult<HashMap<crate::debugger::variable_scope::ScopeType, Vec<crate::debugger::variable_scope::VariableInfo>>> {
let inspector: _ = VariableInspector::new(self.config.clone());
let scopes: _ = Vec::new();
let result: _ = inspector.get_all_scope_variables(&scopes);
if result.success {
if let Some(data) = result.data {
DebugResult::ok(data)
} else {
DebugResult::err("No data returned".to_string())
}
} else {
DebugResult::err(result.error.unwrap_or_else(|| "Unknown error".to_string()))
}
}
pub fn print_stack_trace(&self) {
if let Some(stack) = self.get_stack_trace() {
println!("{}", stack.to_string());
} else {
println!("No stack trace available");
}
}
pub fn is_running(&self) -> bool {
let state: _ = self.state.lock().unwrap();
matches!(*state, DebugState::Running)
}
pub fn is_paused(&self) -> bool {
let state: _ = self.state.lock().unwrap();
matches!(*state, DebugState::Paused)
}
pub fn is_stepping(&self) -> bool {
let state: _ = self.state.lock().unwrap();
matches!(*state, DebugState::Stepping)
}
pub fn add_watch(&mut self, expression: &str) -> DebugResult<WatchExpression> {
match self.watch_manager.add(expression) {
Ok(watch) => {
let mut stats = self.stats.lock().unwrap();
stats.watches_added += 1;
DebugResult::ok(watch)
}
Err(e) => DebugResult::err(e),
}
}
pub fn remove_watch(&mut self, id: &str) -> DebugResult<()> {
match self.watch_manager.remove(id) {
Ok(()) => DebugResult::ok(()),
Err(e) => DebugResult::err(e),
}
}
pub fn get_all_watches(&self) -> Vec<WatchExpression> {
self.watch_manager.list().into_iter().cloned().collect()
}
pub fn get_watch_count(&self) -> usize {
self.watch_manager.count()
}
pub fn clear_all_watches(&mut self) {
self.watch_manager.clear();
}
pub fn update_watch_value(
&mut self,
id: &str,
value: &str,
value_type: &str,
) -> DebugResult<()> {
match self.watch_manager.update_value(id, value, value_type) {
Ok(()) => DebugResult::ok(()),
Err(e) => DebugResult::err(e),
}
}
pub fn set_watch_error(&mut self, id: &str, error: &str) -> DebugResult<()> {
match self.watch_manager.set_error(id, error) {
Ok(()) => DebugResult::ok(()),
Err(e) => DebugResult::err(e),
}
}
pub fn evaluate_watch_expression(
&self,
expression: &str,
runtime: &RuntimeLite,
) -> DebugResult<(String, String)> {
match runtime.execute_code(expression) {
Ok(value) => {
let value_type: _ = match value.as_str() {
"null" | "undefined" => "primitive".to_string(),
_ if value.parse::<i64>().is_ok() => "number".to_string(),
_ if value.parse::<f64>().is_ok() => "number".to_string(),
_ if value == "true" || value == "false" => "boolean".to_string(),
_ if value.starts_with('[') || value.starts_with('{') => "object".to_string(),
_ if value.starts_with('"') || value.starts_with('\'') => "string".to_string(),
_ => "unknown".to_string(),
};
DebugResult::ok((value, value_type))
}
Err(e) => {
let error_msg: _ = format!("Evaluation error: {}", e);
DebugResult::err(error_msg)
}
}
}
pub fn evaluate_all_watches(
&mut self,
runtime: &RuntimeLite,
) -> DebugResult<Vec<(String, String, String)>> {
let mut results = Vec::new();
let watches: _ = self.watch_manager.list();
let watch_data: Vec<(String, String)> = watches
.into_iter()
.map(|watch| (watch.id.clone(), watch.expression.clone()))
.collect();
for (watch_id, expression) in watch_data {
let eval_result: _ = self.evaluate_watch_expression(&expression, runtime);
if eval_result.success {
if let Some((value, value_type)) = eval_result.data {
if let Err(e) = self.watch_manager.update_value(&watch_id, &value, &value_type) {
eprintln!("Warning: Failed to update watch {}: {:?}", watch_id, e);
}
results.push((watch_id, value, value_type));
}
} else {
let error_msg: _ = eval_result.error.unwrap_or_else(|| "Unknown error".to_string());
if let Err(e) = self.watch_manager.set_error(&watch_id, &error_msg) {
eprintln!("Warning: Failed to set error on watch {}: {:?}", watch_id, e);
}
results.push((watch_id, format!("<error: {}>", error_msg), "error".to_string()))
}
}
DebugResult::ok(results)
}
}