runmat_runtime/
debug_context.rs1use crate::source_context;
2use runmat_thread_local::runmat_thread_local;
3use runmat_types::SourceId;
4use std::cell::RefCell;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct DebugFrame {
8 pub function: String,
9 pub source_id: Option<SourceId>,
10 pub span: Option<(usize, usize)>,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct DebugFrameInfo {
15 pub function: String,
16 pub file: String,
17 pub line: usize,
18}
19
20runmat_thread_local! {
21 static DEBUG_STACK: RefCell<Vec<DebugFrame>> = const { RefCell::new(Vec::new()) };
22}
23
24pub struct DebugFrameGuard {
25 did_push: bool,
26 state: Option<std::rc::Rc<crate::context::RuntimeContextState>>,
27}
28
29impl Drop for DebugFrameGuard {
30 fn drop(&mut self) {
31 if !self.did_push {
32 return;
33 }
34 if let Some(state) = &self.state {
35 state.debug.borrow_mut().pop();
36 } else {
37 DEBUG_STACK.with(|stack| {
38 let mut stack = stack.borrow_mut();
39 let _ = stack.pop();
40 });
41 }
42 }
43}
44
45pub fn push_frame(
46 function: impl Into<String>,
47 source_id: Option<SourceId>,
48 span: Option<(usize, usize)>,
49) -> DebugFrameGuard {
50 let frame = DebugFrame {
51 function: function.into(),
52 source_id,
53 span,
54 };
55 if let Some(state) = active_state() {
56 state.debug.borrow_mut().push(frame);
57 DebugFrameGuard {
58 did_push: true,
59 state: Some(state),
60 }
61 } else {
62 DEBUG_STACK.with(|stack| stack.borrow_mut().push(frame));
63 DebugFrameGuard {
64 did_push: true,
65 state: None,
66 }
67 }
68}
69
70pub fn current_frames() -> Vec<DebugFrameInfo> {
71 if let Some(state) = active_state() {
72 return state.debug.borrow().iter().rev().map(frame_info).collect();
73 }
74 DEBUG_STACK.with(|stack| {
75 stack
76 .borrow()
77 .iter()
78 .rev()
79 .map(frame_info)
80 .collect::<Vec<_>>()
81 })
82}
83
84pub fn current_function_name() -> Option<String> {
85 if let Some(state) = active_state() {
86 return state
87 .debug
88 .borrow()
89 .last()
90 .map(|frame| frame.function.clone());
91 }
92 DEBUG_STACK.with(|stack| stack.borrow().last().map(|frame| frame.function.clone()))
93}
94
95pub fn reset_for_tests() {
96 if let Some(state) = active_state() {
97 state.debug.borrow_mut().clear();
98 return;
99 }
100 DEBUG_STACK.with(|stack| stack.borrow_mut().clear());
101}
102
103fn active_state() -> Option<std::rc::Rc<crate::context::RuntimeContextState>> {
104 crate::context::legacy::active().map(|context| std::rc::Rc::clone(context.state()))
105}
106
107fn frame_info(frame: &DebugFrame) -> DebugFrameInfo {
108 let source = frame.source_id.and_then(source_context::source_info);
109 let file = source
110 .as_ref()
111 .map(|source| {
112 source
113 .fullpath_name
114 .as_ref()
115 .map(ToString::to_string)
116 .unwrap_or_else(|| source.name.to_string())
117 })
118 .unwrap_or_default();
119 let line = frame
120 .span
121 .and_then(|(start, _)| {
122 source
123 .as_ref()
124 .map(|source| line_for_offset(&source.text, start))
125 })
126 .unwrap_or(0);
127 DebugFrameInfo {
128 function: frame.function.clone(),
129 file,
130 line,
131 }
132}
133
134fn line_for_offset(source: &str, offset: usize) -> usize {
135 let offset = offset.min(source.len());
136 source[..offset]
137 .bytes()
138 .filter(|byte| *byte == b'\n')
139 .count()
140 + 1
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn debug_frame_snapshot_uses_source_catalog() {
149 let source_id = SourceId(7);
150 let _catalog = source_context::replace_source_catalog_with_fullpaths(vec![(
151 source_id,
152 "demo.m".to_string(),
153 Some("/tmp/demo.m".to_string()),
154 "a = 1;\nb = 2;\n".to_string(),
155 )]);
156 let _guard = push_frame("demo", Some(source_id), Some((8, 13)));
157 let frames = current_frames();
158 assert_eq!(frames.len(), 1);
159 assert_eq!(frames[0].function, "demo");
160 assert_eq!(frames[0].file, "/tmp/demo.m");
161 assert_eq!(frames[0].line, 2);
162 }
163}