Skip to main content

runmat_vm/runtime/
call_stack.rs

1use crate::bytecode::Bytecode;
2use miette::SourceSpan;
3use runmat_runtime::{CallFrame, RuntimeError};
4use runmat_thread_local::runmat_thread_local;
5use std::cell::{Cell, RefCell};
6
7pub const DEFAULT_CALLSTACK_LIMIT: usize = 200;
8pub const DEFAULT_ERROR_NAMESPACE: &str = "RunMat";
9
10#[derive(Default, Clone)]
11struct CallStackState {
12    frames: Vec<CallFrame>,
13    depth: usize,
14}
15
16runmat_thread_local! {
17    static CALL_STACK: RefCell<CallStackState> = const {
18        RefCell::new(CallStackState {
19            frames: Vec::new(),
20            depth: 0,
21        })
22    };
23    static CALL_STACK_LIMIT: Cell<usize> = const { Cell::new(DEFAULT_CALLSTACK_LIMIT) };
24    static ERROR_NAMESPACE: RefCell<String> = const {
25        RefCell::new(String::new())
26    };
27}
28
29pub fn callstack_limit() -> usize {
30    CALL_STACK_LIMIT.with(|limit| limit.get())
31}
32
33pub fn error_namespace() -> String {
34    let ns = ERROR_NAMESPACE.with(|ns| ns.borrow().clone());
35    if ns.trim().is_empty() {
36        DEFAULT_ERROR_NAMESPACE.to_string()
37    } else {
38        ns
39    }
40}
41
42pub fn set_error_namespace(namespace: &str) {
43    let namespace = if namespace.trim().is_empty() {
44        DEFAULT_ERROR_NAMESPACE
45    } else {
46        namespace
47    };
48    ERROR_NAMESPACE.with(|ns| {
49        *ns.borrow_mut() = namespace.to_string();
50    });
51}
52
53pub fn set_call_stack_limit(limit: usize) {
54    CALL_STACK_LIMIT.with(|cell| cell.set(limit));
55    CALL_STACK.with(|stack| {
56        let mut stack = stack.borrow_mut();
57        if limit == 0 {
58            stack.frames.clear();
59        } else if stack.frames.len() > limit {
60            while stack.frames.len() > limit {
61                stack.frames.remove(0);
62            }
63        }
64    });
65}
66
67pub fn attach_call_frames(
68    bytecode: &Bytecode,
69    current_function_name: &str,
70    mut err: RuntimeError,
71) -> RuntimeError {
72    if !err.context.call_frames.is_empty() || !err.context.call_stack.is_empty() {
73        return err;
74    }
75    let (mut frames, depth) = CALL_STACK.with(|stack| {
76        let stack = stack.borrow();
77        let frames = stack.frames.clone();
78        (frames, stack.depth)
79    });
80    let limit = callstack_limit();
81    if frames.is_empty() {
82        if limit == 0 {
83            return err;
84        }
85        let span = err.span.as_ref().map(|span: &SourceSpan| {
86            let start = span.offset();
87            let end = start + span.len();
88            (start, end)
89        });
90        if span.is_some() || !current_function_name.is_empty() {
91            frames.push(CallFrame {
92                function: current_function_name.to_string(),
93                source_id: bytecode.source_id.map(|id| id.0),
94                span,
95            });
96        }
97    }
98    let elided = if frames.is_empty() {
99        0
100    } else {
101        depth.saturating_sub(frames.len())
102    };
103    err.context.call_frames = frames;
104    err.context.call_frames_elided = elided;
105    err
106}