1use rustc_hash::FxHashMap as HashMap;
2
3use crate::{
4 lambda::runnable::RuntimeError,
5 types::{
6 lambda::vm_instructions::instruction_set::VMInstructionPackage, object::OnionStaticObject,
7 },
8 utils::format_object_summary,
9};
10
11#[derive(Clone, Debug)]
12pub struct Frame {
13 pub variables: HashMap<usize, OnionStaticObject>,
14 pub stack: Vec<OnionStaticObject>,
15}
16
17impl Frame {
18 #[inline(always)]
19 pub fn get_stack(&self) -> &Vec<OnionStaticObject> {
20 &self.stack
21 }
22 #[inline(always)]
23 pub fn get_stack_mut(&mut self) -> &mut Vec<OnionStaticObject> {
24 &mut self.stack
25 }
26
27 pub fn format_context(&self, package: &VMInstructionPackage) -> String {
28 let mut parts = Vec::new();
29 let string_pool = package.get_string_pool();
30
31 if self.variables.is_empty() {
33 parts.push(" - Variables: (none)".to_string());
34 } else {
35 parts.push(" - Variables:".to_string());
36 for (id, value) in &self.variables {
37 let var_name = string_pool
39 .get(*id)
40 .map(|s| s.as_ref())
41 .unwrap_or("<Unknown Var>");
42
43 let value_summary = format_object_summary(value.weak());
44 parts.push(format!(" - {}: {}", var_name, value_summary));
45 }
46 }
47
48 if self.stack.is_empty() {
50 parts.push(" - Operand Stack: (empty)".to_string());
51 } else {
52 parts.push(format!(" - Operand Stack ({} items):", self.stack.len()));
53 for (i, value) in self.stack.iter().rev().enumerate() {
54 let value_summary = format_object_summary(value.weak());
56 parts.push(format!(" - [Top - {}]: {}", i, value_summary));
57 }
58 }
59
60 parts.join("\n")
61 }
62}
63
64#[derive(Clone)]
65pub struct Context {
66 pub(crate) frames: Vec<Frame>,
67}
68
69impl Context {
70 pub fn new() -> Self {
71 Context { frames: Vec::new() }
72 }
73
74 pub fn push_frame(&mut self, frame: Frame) {
75 self.frames.push(frame);
76 }
77
78 pub fn pop_frame(&mut self) -> Result<Frame, RuntimeError> {
79 match self.frames.pop() {
80 Some(frame) => Ok(frame),
81 None => Err(RuntimeError::DetailedError(
82 "Cannot pop frame from empty context".into(),
83 )),
84 }
85 }
86 pub fn concat_last_frame(&mut self) -> Result<(), RuntimeError> {
87 if self.frames.len() < 2 {
88 return Ok(());
89 }
90
91 let last_frame = self.frames.pop().unwrap();
92 let second_last_frame = self.frames.last_mut().unwrap();
93 second_last_frame.stack.extend(last_frame.stack);
94 Ok(())
95 }
96 pub fn clear_stack(&mut self) {
97 if self.frames.len() > 0 {
98 self.frames.last_mut().unwrap().stack.clear();
99 }
100 }
101
102 pub fn push_object(&mut self, object: OnionStaticObject) -> Result<(), RuntimeError> {
103 if self.frames.len() == 0 {
104 return Err(RuntimeError::DetailedError(
105 "Cannot push object to empty context".into(),
106 ));
107 }
108 self.frames.last_mut().unwrap().stack.push(object);
109 Ok(())
110 }
111
112 pub fn pop(&mut self) -> Result<OnionStaticObject, RuntimeError> {
113 if self.frames.len() == 0 {
114 return Err(RuntimeError::DetailedError(
115 "Cannot pop object from empty context".into(),
116 ));
117 }
118 let last_frame = self.frames.last_mut().unwrap();
119 if last_frame.get_stack().len() == 0 {
120 return Err(RuntimeError::DetailedError(
121 "Cannot pop object from empty stack".into(),
122 ));
123 }
124 let stack = last_frame.get_stack_mut();
125 Ok(stack.pop().unwrap())
126 }
127
128 pub fn discard_objects(&mut self, count: usize) -> Result<(), RuntimeError> {
129 if self.frames.len() == 0 {
130 return Err(RuntimeError::DetailedError(
131 "Cannot discard objects from empty context"
132 .to_string()
133 .into(),
134 ));
135 }
136 let last_frame = self.frames.last_mut().unwrap();
137 let stack = last_frame.get_stack_mut();
138 if stack.len() < count {
139 return Err(RuntimeError::DetailedError(
140 "Cannot discard more objects than available in stack"
141 .to_string()
142 .into(),
143 ));
144 }
145 stack.truncate(stack.len() - count);
149 Ok(())
150 }
151
152 pub fn discard_objects_offset(
153 &mut self,
154 offset: usize,
155 count: usize,
156 ) -> Result<(), RuntimeError> {
157 if self.frames.len() == 0 {
158 return Err(RuntimeError::DetailedError(
159 "Cannot discard objects from empty context"
160 .to_string()
161 .into(),
162 ));
163 }
164 let last_frame = self.frames.last_mut().unwrap();
165 let stack = last_frame.get_stack_mut();
166 if stack.len() < offset + count {
167 return Err(RuntimeError::DetailedError(
168 "Cannot discard more objects than available in stack"
169 .to_string()
170 .into(),
171 ));
172 }
173
174 let remove_start = stack.len() - offset - count;
176 let remove_end = stack.len() - offset;
177 stack.drain(remove_start..remove_end);
178 Ok(())
179 }
180
181 pub fn get_object_rev(&self, idx: usize) -> Result<&OnionStaticObject, RuntimeError> {
182 if self.frames.len() == 0 {
183 return Err(RuntimeError::DetailedError(
184 "Cannot get object from empty context".into(),
185 ));
186 }
187 let last_frame = self.frames.last().unwrap();
188 if last_frame.get_stack().len() <= idx {
189 return Err(RuntimeError::DetailedError(
190 "Cannot get object from empty stack".into(),
191 ));
192 }
193 let stack = last_frame.get_stack();
194 match stack.get(stack.len() - 1 - idx) {
195 None => Err(RuntimeError::DetailedError(
196 "Index out of bounds".into(),
197 )),
198 Some(o) => Ok(o),
199 }
200 }
201
202 pub fn get_object_rev_mut(
203 &mut self,
204 idx: usize,
205 ) -> Result<&mut OnionStaticObject, RuntimeError> {
206 if self.frames.len() == 0 {
207 return Err(RuntimeError::DetailedError(
208 "Cannot get object from empty context".into(),
209 ));
210 }
211 let last_frame = self.frames.last_mut().unwrap();
212 if last_frame.get_stack().len() <= idx {
213 return Err(RuntimeError::DetailedError(
214 "Cannot get object from empty stack".into(),
215 ));
216 }
217 let stack = last_frame.get_stack_mut();
218 let idx = stack.len() - 1 - idx;
219 match stack.get_mut(idx) {
220 None => Err(RuntimeError::DetailedError(
221 "Index out of bounds".into(),
222 )),
223 Some(o) => Ok(o),
224 }
225 }
226
227 #[inline(always)]
228 pub fn let_variable(
229 &mut self,
230 name: usize,
231 value: OnionStaticObject,
232 ) -> Result<(), RuntimeError> {
233 if self.frames.len() == 0 {
234 return Err(RuntimeError::InvalidOperation(
235 "Cannot let variable in empty context".into(),
236 ));
237 }
238
239 let last_frame = self.frames.last_mut().unwrap();
240 last_frame.variables.insert(name, value);
241 Ok(())
242 }
243
244 #[inline(always)]
245 pub fn get_variable(&self, name: usize) -> Option<&OnionStaticObject> {
246 if self.frames.len() == 0 {
247 return None;
248 } for frame in self.frames.iter().rev() {
250 if let Some(value) = frame.variables.get(&name) {
251 return Some(value);
252 }
253 }
254 None
255 }
256
257 fn _debug_print(&self) {
258 println!("Context Debug Print:");
259 for (i, frame) in self.frames.iter().enumerate() {
260 println!("Frame {}: {:?}", i, frame);
261 }
262 }
263
264 pub fn get_variable_mut(&mut self, name: usize) -> Option<&mut OnionStaticObject> {
265 if self.frames.len() == 0 {
266 return None;
267 } for frame in self.frames.iter_mut().rev() {
269 if let Some(value) = frame.variables.get_mut(&name) {
270 return Some(value);
271 }
272 }
273 None
274 }
275
276 pub fn swap(&mut self, idx1: usize, idx2: usize) -> Result<(), RuntimeError> {
277 if self.frames.len() == 0 {
278 return Err(RuntimeError::DetailedError(
279 "Cannot swap objects in empty context".into(),
280 ));
281 }
282 let last_frame = self.frames.last_mut().unwrap();
283 let stack = last_frame.get_stack_mut();
284 if stack.len() <= idx1 || stack.len() <= idx2 {
285 return Err(RuntimeError::DetailedError(
286 "Cannot swap objects in empty stack".into(),
287 ));
288 }
289 let len = stack.len();
290 stack.swap(len - 1 - idx1, len - 1 - idx2);
291 Ok(())
292 }
293
294 pub fn get_current_stack_mut(&mut self) -> Result<&mut Vec<OnionStaticObject>, RuntimeError> {
295 if self.frames.len() == 0 {
296 return Err(RuntimeError::DetailedError(
297 "Cannot get stack from empty context".into(),
298 ));
299 }
300 let last_frame = self.frames.last_mut().unwrap();
301 Ok(last_frame.get_stack_mut())
302 }
303
304 #[inline(always)]
305 pub fn push_to_stack(stack: &mut Vec<OnionStaticObject>, object: OnionStaticObject) {
306 stack.push(object);
307 }
308
309 pub fn pop_from_stack(
310 stack: &mut Vec<OnionStaticObject>,
311 ) -> Result<OnionStaticObject, RuntimeError> {
312 if stack.is_empty() {
313 return Err(RuntimeError::DetailedError(
314 "Cannot pop from empty stack".into(),
315 ));
316 }
317 Ok(stack.pop().unwrap())
318 }
319
320 #[inline(always)]
321 pub fn discard_from_stack(
322 stack: &mut Vec<OnionStaticObject>,
323 count: usize,
324 ) -> Result<(), RuntimeError> {
325 if stack.len() < count {
326 return Err(RuntimeError::DetailedError(
327 "Cannot discard more objects than available in stack"
328 .to_string()
329 .into(),
330 ));
331 }
332 stack.truncate(stack.len() - count);
333 Ok(())
334 }
335
336 #[inline(always)]
337 pub fn get_object_from_stack(
338 stack: &Vec<OnionStaticObject>,
339 idx: usize,
340 ) -> Result<&OnionStaticObject, RuntimeError> {
341 if stack.len() <= idx {
342 return Err(RuntimeError::DetailedError(
343 "Index out of bounds".into(),
344 ));
345 }
346 Ok(&stack[stack.len() - 1 - idx])
347 }
348
349 pub fn replace_last_object(stack: &mut Vec<OnionStaticObject>, object: OnionStaticObject) {
350 let last_index = stack.len() - 1;
351 stack[last_index] = object;
352 }
353
354 pub fn format_context(&self, package: &VMInstructionPackage) -> String {
355 if self.frames.is_empty() {
356 return "Context: (No active frames)".to_string();
357 }
358
359 let mut parts = Vec::new();
360 parts.push(format!("Call Stack ({} frames):", self.frames.len()));
361
362 for (i, frame) in self.frames.iter().rev().enumerate() {
364 parts.push(format!("--- Frame #{} (most recent) ---", i));
367
368 let frame_context = frame.format_context(package);
370 parts.push(frame_context);
371 }
372
373 parts.join("\n")
374 }
375}