Skip to main content

runmat_runtime/
debug_context.rs

1use 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}