use rusty_v8 as v8;
use crate::debugger::{SourceLocation, DebugResult, v8_stubs::DebugExecutionState};
use std::collections::{HashMap, BTreeMap};
#[derive(Debug, Clone)]
pub struct StackFrame {
pub index: u32,
pub function_name: String,
pub script_id: String,
pub script_name: String,
pub line_number: u32,
pub column_number: u32,
pub is_eval: bool,
pub is_constructor: bool,
pub is_async: bool,
}
impl StackFrame {
pub fn get_location(&self) -> SourceLocation {
SourceLocation {
script_id: self.script_id.clone(),
script_name: self.script_name.clone(),
line_number: self.line_number,
column_number: self.column_number,
}
}
pub fn is_at_location(&self, script_id: &str, line_number: u32) -> bool {
self.script_id == script_id && self.line_number == line_number
}
}
#[derive(Debug, Clone)]
pub struct StackFrameInfo {
pub frame: StackFrame,
pub arguments: Vec<FrameVariable>,
pub locals: Vec<FrameVariable>,
pub closure: Option<Vec<FrameVariable>>,
}
#[derive(Debug, Clone)]
pub struct FrameVariable {
pub name: String,
pub value: String,
pub type_name: String,
}
#[derive(Debug, Clone)]
pub struct StackTrace {
pub frames: Vec<StackFrame>,
pub total_frames: usize,
pub is_truncated: bool,
}
impl StackTrace {
pub fn new() -> Self {
Self {
frames: Vec::new(),
total_frames: 0,
is_truncated: false,
}
}
pub fn current_frame(&self) -> Option<&StackFrame> {
self.frames.first()
}
pub fn bottom_frame(&self) -> Option<&StackFrame> {
self.frames.last()
}
pub fn get_frame(&self, index: usize) -> Option<&StackFrame> {
self.frames.get(index)
}
pub fn frame_count(&self) -> usize {
self.frames.len()
}
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn find_frames_in_script(&self, script_id: &str) -> Vec<&StackFrame> {
self.frames
.iter()
.filter(|frame| frame.script_id == script_id)
.collect()
}
pub fn find_frames_at_location(
&self,
script_id: &str,
line_number: u32,
) -> Vec<&StackFrame> {
self.frames
.iter()
.filter(|frame| frame.is_at_location(script_id, line_number))
.collect()
}
pub fn simplified(&self) -> Vec<String> {
self.frames
.iter()
.map(|frame| {
format!(
" at {} ({}:{}:{})",
frame.function_name,
frame.script_name,
frame.line_number,
frame.column_number
)
})
.collect()
}
pub fn to_string(&self) -> String {
if self.frames.is_empty() {
"Empty stack".to_string()
} else {
let mut result = String::new();
result.push_str("Call Stack:\n");
for (i, frame) in self.frames.iter().enumerate() {
result.push_str(&format!(
" {}: {} ({}:{}:{})\n",
i,
frame.function_name,
frame.script_name,
frame.line_number,
frame.column_number
));
}
if self.is_truncated {
result.push_str(&format!(
" ... and {} more frames\n",
self.total_frames - self.frames.len()));
}
result
}
}
}
impl Default for StackTrace {
fn default() -> Self {
Self::new()
}
}
pub struct StackFrameBuilder {
frame_index: u32,
}
impl StackFrameBuilder {
pub fn new() -> Self {
Self { frame_index: 0 }
}
pub fn build_from_v8(
&mut self,
exec_state: &v8::Global<DebugExecutionState>,
) -> DebugResult<StackFrame> {
let frame: _ = StackFrame {
index: self.frame_index,
function_name: "Unknown".to_string(),
script_id: "Unknown".to_string(),
script_name: "Unknown".to_string(),
line_number: 0,
column_number: 0,
is_eval: false,
is_constructor: false,
is_async: false,
};
self.frame_index += 1;
DebugResult::ok(frame)
}
pub fn build_stack_trace_from_v8(
&mut self,
exec_state: &v8::Global<DebugExecutionState>,
max_frames: usize,
) -> DebugResult<StackTrace> {
let mut frames = Vec::new();
let mut is_truncated = false;
let total_frames: _ = 0;
for i in 0..std::cmp::min(max_frames, total_frames) {
let frame: _ = StackFrame {
index: i as u32,
function_name: format!("frame_{}", i),
script_id: "script_0".to_string(),
script_name: "unknown.js".to_string(),
line_number: 1,
column_number: 1,
is_eval: false,
is_constructor: false,
is_async: false,
};
frames.push(frame);
}
if total_frames > max_frames {
is_truncated = true;
}
DebugResult::ok(StackTrace {
frames,
total_frames,
is_truncated,
})
}
pub fn reset(&mut self) {
self.frame_index = 0;
}
}
impl Default for StackFrameBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct StackTraverser {
current_index: usize,
}
impl StackTraverser {
pub fn new() -> Self {
Self { current_index: 0 }
}
pub fn next_frame<'a>(&mut self, stack: &'a StackTrace) -> Option<&'a StackFrame> {
if self.current_index < stack.frames.len() {
let frame: _ = &stack.frames[self.current_index];
self.current_index += 1;
Some(frame)
} else {
None
}
}
pub fn previous_frame<'a>(&mut self, stack: &'a StackTrace) -> Option<&'a StackFrame> {
if self.current_index > 0 {
self.current_index -= 1;
Some(&stack.frames[self.current_index])
} else {
None
}
}
pub fn current_frame<'a>(&self, stack: &'a StackTrace) -> Option<&'a StackFrame> {
if self.current_index < stack.frames.len() {
Some(&stack.frames[self.current_index])
} else {
None
}
}
pub fn reset(&mut self) {
self.current_index = 0;
}
pub fn jump_to_frame<'a>(&mut self, stack: &'a StackTrace, index: usize) -> Option<&'a StackFrame> {
if index < stack.frames.len() {
self.current_index = index;
Some(&stack.frames[index])
} else {
None
}
}
}
impl Default for StackTraverser {
fn default() -> Self {
Self::new()
}
}