1use std::rc::Rc;
2use std::time::Instant;
3
4use crate::chunk::{Chunk, ChunkRef};
5use crate::value::{ModuleFunctionRegistry, VmError, VmValue};
6
7use super::{CallFrame, LocalSlot, Vm};
8
9impl Vm {
10 pub async fn execute(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
12 let span_id = crate::tracing::span_start(crate::tracing::SpanKind::Pipeline, "main".into());
13 let result = self.run_chunk(chunk).await;
14 crate::tracing::span_end(span_id);
15 result
16 }
17
18 pub(crate) fn handle_error(&mut self, error: VmError) -> Result<Option<VmValue>, VmError> {
20 let thrown_value = match &error {
21 VmError::Thrown(v) => v.clone(),
22 other => VmValue::String(Rc::from(other.to_string())),
23 };
24
25 if let Some(handler) = self.exception_handlers.pop() {
26 if !handler.error_type.is_empty() {
27 let matches = match &thrown_value {
29 VmValue::EnumVariant { enum_name, .. } => {
30 enum_name.as_ref() == handler.error_type
31 }
32 _ => false,
33 };
34 if !matches {
35 return self.handle_error(error);
36 }
37 }
38
39 while self.frames.len() > handler.frame_depth {
40 if let Some(frame) = self.frames.pop() {
41 if let Some(ref dir) = frame.saved_source_dir {
42 crate::stdlib::set_thread_source_dir(dir);
43 }
44 self.iterators.truncate(frame.saved_iterator_depth);
45 self.env = frame.saved_env;
46 }
47 }
48
49 while self
51 .deadlines
52 .last()
53 .is_some_and(|d| d.1 > handler.frame_depth)
54 {
55 self.deadlines.pop();
56 }
57
58 self.env.truncate_scopes(handler.env_scope_depth);
59
60 self.stack.truncate(handler.stack_depth);
61 self.stack.push(thrown_value);
62
63 if let Some(frame) = self.frames.last_mut() {
64 frame.ip = handler.catch_ip;
65 }
66
67 Ok(None)
68 } else {
69 Err(error)
70 }
71 }
72
73 pub(crate) async fn run_chunk(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
74 self.run_chunk_entry(chunk, 0, None, None, None, None).await
75 }
76
77 pub(crate) async fn run_chunk_entry(
78 &mut self,
79 chunk: &Chunk,
80 argc: usize,
81 saved_source_dir: Option<std::path::PathBuf>,
82 module_functions: Option<ModuleFunctionRegistry>,
83 module_state: Option<crate::value::ModuleState>,
84 local_slots: Option<Vec<LocalSlot>>,
85 ) -> Result<VmValue, VmError> {
86 self.run_chunk_ref(
87 Rc::new(chunk.clone()),
88 argc,
89 saved_source_dir,
90 module_functions,
91 module_state,
92 local_slots,
93 )
94 .await
95 }
96
97 pub(crate) async fn run_chunk_ref(
98 &mut self,
99 chunk: ChunkRef,
100 argc: usize,
101 saved_source_dir: Option<std::path::PathBuf>,
102 module_functions: Option<ModuleFunctionRegistry>,
103 module_state: Option<crate::value::ModuleState>,
104 local_slots: Option<Vec<LocalSlot>>,
105 ) -> Result<VmValue, VmError> {
106 let initial_env = self.env.clone();
107 let local_slots = local_slots.unwrap_or_else(|| Self::fresh_local_slots(&chunk));
108 let initial_local_slots = local_slots.clone();
109 self.frames.push(CallFrame {
110 chunk,
111 ip: 0,
112 stack_base: self.stack.len(),
113 saved_env: self.env.clone(),
114 initial_env: Some(initial_env),
115 initial_local_slots: Some(initial_local_slots),
116 saved_iterator_depth: self.iterators.len(),
117 fn_name: String::new(),
118 argc,
119 saved_source_dir,
120 module_functions,
121 module_state,
122 local_slots,
123 local_scope_base: self.env.scope_depth().saturating_sub(1),
124 local_scope_depth: 0,
125 });
126
127 loop {
128 if let Some(&(deadline, _)) = self.deadlines.last() {
129 if Instant::now() > deadline {
130 self.deadlines.pop();
131 let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
132 match self.handle_error(err) {
133 Ok(None) => continue,
134 Ok(Some(val)) => return Ok(val),
135 Err(e) => return Err(e),
136 }
137 }
138 }
139
140 let frame = match self.frames.last_mut() {
141 Some(f) => f,
142 None => return Ok(self.stack.pop().unwrap_or(VmValue::Nil)),
143 };
144
145 if frame.ip >= frame.chunk.code.len() {
146 let val = self.stack.pop().unwrap_or(VmValue::Nil);
147 let popped_frame = self.frames.pop().unwrap();
148 if let Some(ref dir) = popped_frame.saved_source_dir {
149 crate::stdlib::set_thread_source_dir(dir);
150 }
151
152 if self.frames.is_empty() {
153 return Ok(val);
154 } else {
155 self.iterators.truncate(popped_frame.saved_iterator_depth);
156 self.env = popped_frame.saved_env;
157 self.stack.truncate(popped_frame.stack_base);
158 self.stack.push(val);
159 continue;
160 }
161 }
162
163 let op = frame.chunk.code[frame.ip];
164 frame.ip += 1;
165
166 match self.execute_op(op).await {
167 Ok(Some(val)) => return Ok(val),
168 Ok(None) => continue,
169 Err(VmError::Return(val)) => {
170 if let Some(popped_frame) = self.frames.pop() {
171 if let Some(ref dir) = popped_frame.saved_source_dir {
172 crate::stdlib::set_thread_source_dir(dir);
173 }
174 let current_depth = self.frames.len();
175 self.exception_handlers
176 .retain(|h| h.frame_depth <= current_depth);
177
178 if self.frames.is_empty() {
179 return Ok(val);
180 }
181 self.iterators.truncate(popped_frame.saved_iterator_depth);
182 self.env = popped_frame.saved_env;
183 self.stack.truncate(popped_frame.stack_base);
184 self.stack.push(val);
185 } else {
186 return Ok(val);
187 }
188 }
189 Err(e) => {
190 if self.error_stack_trace.is_empty() {
192 self.error_stack_trace = self.capture_stack_trace();
193 }
194 match self.handle_error(e) {
195 Ok(None) => {
196 self.error_stack_trace.clear();
197 continue;
198 }
199 Ok(Some(val)) => return Ok(val),
200 Err(e) => return Err(self.enrich_error_with_line(e)),
201 }
202 }
203 }
204 }
205 }
206
207 pub(crate) async fn execute_one_cycle(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
208 if let Some(&(deadline, _)) = self.deadlines.last() {
209 if Instant::now() > deadline {
210 self.deadlines.pop();
211 let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
212 match self.handle_error(err) {
213 Ok(None) => return Ok(None),
214 Ok(Some(val)) => return Ok(Some((val, false))),
215 Err(e) => return Err(e),
216 }
217 }
218 }
219
220 let frame = match self.frames.last_mut() {
221 Some(f) => f,
222 None => {
223 let val = self.stack.pop().unwrap_or(VmValue::Nil);
224 return Ok(Some((val, false)));
225 }
226 };
227
228 if frame.ip >= frame.chunk.code.len() {
229 let val = self.stack.pop().unwrap_or(VmValue::Nil);
230 let popped_frame = self.frames.pop().unwrap();
231 if self.frames.is_empty() {
232 return Ok(Some((val, false)));
233 } else {
234 self.iterators.truncate(popped_frame.saved_iterator_depth);
235 self.env = popped_frame.saved_env;
236 self.stack.truncate(popped_frame.stack_base);
237 self.stack.push(val);
238 return Ok(None);
239 }
240 }
241
242 let op = frame.chunk.code[frame.ip];
243 frame.ip += 1;
244
245 match self.execute_op(op).await {
246 Ok(Some(val)) => Ok(Some((val, false))),
247 Ok(None) => Ok(None),
248 Err(VmError::Return(val)) => {
249 if let Some(popped_frame) = self.frames.pop() {
250 if let Some(ref dir) = popped_frame.saved_source_dir {
251 crate::stdlib::set_thread_source_dir(dir);
252 }
253 let current_depth = self.frames.len();
254 self.exception_handlers
255 .retain(|h| h.frame_depth <= current_depth);
256 if self.frames.is_empty() {
257 return Ok(Some((val, false)));
258 }
259 self.iterators.truncate(popped_frame.saved_iterator_depth);
260 self.env = popped_frame.saved_env;
261 self.stack.truncate(popped_frame.stack_base);
262 self.stack.push(val);
263 Ok(None)
264 } else {
265 Ok(Some((val, false)))
266 }
267 }
268 Err(e) => {
269 if self.error_stack_trace.is_empty() {
270 self.error_stack_trace = self.capture_stack_trace();
271 }
272 match self.handle_error(e) {
273 Ok(None) => {
274 self.error_stack_trace.clear();
275 Ok(None)
276 }
277 Ok(Some(val)) => Ok(Some((val, false))),
278 Err(e) => Err(self.enrich_error_with_line(e)),
279 }
280 }
281 }
282 }
283
284 pub(crate) fn capture_stack_trace(&self) -> Vec<(String, usize, usize, Option<String>)> {
286 self.frames
287 .iter()
288 .map(|f| {
289 let idx = if f.ip > 0 { f.ip - 1 } else { 0 };
290 let line = f.chunk.lines.get(idx).copied().unwrap_or(0) as usize;
291 let col = f.chunk.columns.get(idx).copied().unwrap_or(0) as usize;
292 (f.fn_name.clone(), line, col, f.chunk.source_file.clone())
293 })
294 .collect()
295 }
296
297 pub(crate) fn enrich_error_with_line(&self, error: VmError) -> VmError {
301 let line = self
303 .error_stack_trace
304 .last()
305 .map(|(_, l, _, _)| *l)
306 .unwrap_or_else(|| self.current_line());
307 if line == 0 {
308 return error;
309 }
310 let suffix = format!(" (line {line})");
311 match error {
312 VmError::Runtime(msg) => VmError::Runtime(format!("{msg}{suffix}")),
313 VmError::TypeError(msg) => VmError::TypeError(format!("{msg}{suffix}")),
314 VmError::DivisionByZero => VmError::Runtime(format!("Division by zero{suffix}")),
315 VmError::UndefinedVariable(name) => {
316 VmError::Runtime(format!("Undefined variable: {name}{suffix}"))
317 }
318 VmError::UndefinedBuiltin(name) => {
319 VmError::Runtime(format!("Undefined builtin: {name}{suffix}"))
320 }
321 VmError::ImmutableAssignment(name) => VmError::Runtime(format!(
322 "Cannot assign to immutable binding: {name}{suffix}"
323 )),
324 VmError::StackOverflow => {
325 VmError::Runtime(format!("Stack overflow: too many nested calls{suffix}"))
326 }
327 other => other,
333 }
334 }
335}