Skip to main content

runmat_runtime/
debug_context.rs

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