runmat_runtime/
debug_context.rs1use crate::source_context;
2use runmat_hir::SourceId;
3use runmat_thread_local::runmat_thread_local;
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}
27
28impl Drop for DebugFrameGuard {
29 fn drop(&mut self) {
30 if !self.did_push {
31 return;
32 }
33 DEBUG_STACK.with(|stack| {
34 let mut stack = stack.borrow_mut();
35 let _ = stack.pop();
36 });
37 }
38}
39
40pub fn push_frame(
41 function: impl Into<String>,
42 source_id: Option<SourceId>,
43 span: Option<(usize, usize)>,
44) -> DebugFrameGuard {
45 DEBUG_STACK.with(|stack| {
46 stack.borrow_mut().push(DebugFrame {
47 function: function.into(),
48 source_id,
49 span,
50 });
51 });
52 DebugFrameGuard { did_push: true }
53}
54
55pub fn current_frames() -> Vec<DebugFrameInfo> {
56 DEBUG_STACK.with(|stack| {
57 stack
58 .borrow()
59 .iter()
60 .rev()
61 .map(frame_info)
62 .collect::<Vec<_>>()
63 })
64}
65
66pub fn current_function_name() -> Option<String> {
67 DEBUG_STACK.with(|stack| stack.borrow().last().map(|frame| frame.function.clone()))
68}
69
70pub fn reset_for_tests() {
71 DEBUG_STACK.with(|stack| stack.borrow_mut().clear());
72}
73
74fn frame_info(frame: &DebugFrame) -> DebugFrameInfo {
75 let source = frame.source_id.and_then(source_context::source_info);
76 let file = source
77 .as_ref()
78 .map(|source| {
79 source
80 .fullpath_name
81 .as_ref()
82 .map(ToString::to_string)
83 .unwrap_or_else(|| source.name.to_string())
84 })
85 .unwrap_or_default();
86 let line = frame
87 .span
88 .and_then(|(start, _)| {
89 source
90 .as_ref()
91 .map(|source| line_for_offset(&source.text, start))
92 })
93 .unwrap_or(0);
94 DebugFrameInfo {
95 function: frame.function.clone(),
96 file,
97 line,
98 }
99}
100
101fn line_for_offset(source: &str, offset: usize) -> usize {
102 let offset = offset.min(source.len());
103 source[..offset]
104 .bytes()
105 .filter(|byte| *byte == b'\n')
106 .count()
107 + 1
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn debug_frame_snapshot_uses_source_catalog() {
116 let source_id = SourceId(7);
117 let _catalog = source_context::replace_source_catalog_with_fullpaths(vec![(
118 source_id,
119 "demo.m".to_string(),
120 Some("/tmp/demo.m".to_string()),
121 "a = 1;\nb = 2;\n".to_string(),
122 )]);
123 let _guard = push_frame("demo", Some(source_id), Some((8, 13)));
124 let frames = current_frames();
125 assert_eq!(frames.len(), 1);
126 assert_eq!(frames[0].function, "demo");
127 assert_eq!(frames[0].file, "/tmp/demo.m");
128 assert_eq!(frames[0].line, 2);
129 }
130}