glyph_runtime/
frame.rs

1use glyph_types::Value;
2use std::collections::HashMap;
3
4/// Represents a call frame in the VM's call stack.
5///
6/// Each function call creates a new frame that contains:
7/// - The function being executed
8/// - The instruction pointer within that function
9/// - Local variables
10/// - The stack base pointer for this frame
11#[derive(Debug, Clone)]
12pub struct Frame {
13    /// Index of the function being executed
14    pub function_idx: usize,
15    /// Current instruction pointer within the function
16    pub ip: usize,
17    /// Base pointer in the stack for this frame
18    pub bp: usize,
19    /// Local variables for this frame
20    pub locals: HashMap<String, Value>,
21    /// Return address (instruction pointer to return to)
22    pub return_ip: Option<usize>,
23    /// Function index to return to
24    pub return_function: Option<usize>,
25}
26
27impl Frame {
28    /// Creates a new call frame for a function call
29    pub fn new(function_idx: usize, bp: usize) -> Self {
30        Frame {
31            function_idx,
32            ip: 0,
33            bp,
34            locals: HashMap::new(),
35            return_ip: None,
36            return_function: None,
37        }
38    }
39
40    /// Creates a new call frame with return information
41    pub fn with_return(
42        function_idx: usize,
43        bp: usize,
44        return_ip: usize,
45        return_function: usize,
46    ) -> Self {
47        Frame {
48            function_idx,
49            ip: 0,
50            bp,
51            locals: HashMap::new(),
52            return_ip: Some(return_ip),
53            return_function: Some(return_function),
54        }
55    }
56
57    /// Sets a local variable in this frame
58    pub fn set_local(&mut self, name: String, value: Value) {
59        self.locals.insert(name, value);
60    }
61
62    /// Gets a local variable from this frame
63    pub fn get_local(&self, name: &str) -> Option<&Value> {
64        self.locals.get(name)
65    }
66
67    /// Checks if this is a tail call position
68    /// (i.e., the next instruction would be a return)
69    pub fn is_tail_position(
70        &self,
71        next_instruction: Option<&crate::instruction::Instruction>,
72    ) -> bool {
73        matches!(
74            next_instruction,
75            Some(crate::instruction::Instruction::Return)
76        )
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_frame_creation() {
86        let frame = Frame::new(0, 10);
87        assert_eq!(frame.function_idx, 0);
88        assert_eq!(frame.ip, 0);
89        assert_eq!(frame.bp, 10);
90        assert!(frame.locals.is_empty());
91        assert!(frame.return_ip.is_none());
92        assert!(frame.return_function.is_none());
93    }
94
95    #[test]
96    fn test_frame_with_return() {
97        let frame = Frame::with_return(1, 20, 15, 0);
98        assert_eq!(frame.function_idx, 1);
99        assert_eq!(frame.bp, 20);
100        assert_eq!(frame.return_ip, Some(15));
101        assert_eq!(frame.return_function, Some(0));
102    }
103
104    #[test]
105    fn test_locals() {
106        let mut frame = Frame::new(0, 0);
107
108        frame.set_local("x".to_string(), Value::Int(42));
109        frame.set_local("y".to_string(), Value::Str("hello".to_string()));
110
111        assert_eq!(frame.get_local("x"), Some(&Value::Int(42)));
112        assert_eq!(frame.get_local("y"), Some(&Value::Str("hello".to_string())));
113        assert_eq!(frame.get_local("z"), None);
114    }
115}