runmat_vm/runtime/
call_stack.rs1use 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}