Skip to main content

harn_vm/vm/
execution.rs

1use std::rc::Rc;
2use std::time::Instant;
3
4use crate::chunk::Chunk;
5use crate::value::{ModuleFunctionRegistry, VmError, VmValue};
6
7use super::{CallFrame, Vm};
8
9impl Vm {
10    /// Execute a compiled chunk.
11    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    /// Convert a VmError into either a handled exception (returning Ok) or a propagated error.
19    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                // Typed catch: only match when the thrown enum's type equals the declared type.
28                let matches = match &thrown_value {
29                    VmValue::EnumVariant { enum_name, .. } => *enum_name == handler.error_type,
30                    _ => false,
31                };
32                if !matches {
33                    return self.handle_error(error);
34                }
35            }
36
37            while self.frames.len() > handler.frame_depth {
38                if let Some(frame) = self.frames.pop() {
39                    if let Some(ref dir) = frame.saved_source_dir {
40                        crate::stdlib::set_thread_source_dir(dir);
41                    }
42                    self.iterators.truncate(frame.saved_iterator_depth);
43                    self.env = frame.saved_env;
44                }
45            }
46
47            // Drop deadlines that belonged to unwound frames.
48            while self
49                .deadlines
50                .last()
51                .is_some_and(|d| d.1 > handler.frame_depth)
52            {
53                self.deadlines.pop();
54            }
55
56            self.env.truncate_scopes(handler.env_scope_depth);
57
58            self.stack.truncate(handler.stack_depth);
59            self.stack.push(thrown_value);
60
61            if let Some(frame) = self.frames.last_mut() {
62                frame.ip = handler.catch_ip;
63            }
64
65            Ok(None)
66        } else {
67            Err(error)
68        }
69    }
70
71    pub(crate) async fn run_chunk(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
72        self.run_chunk_entry(chunk, 0, None, None, None).await
73    }
74
75    pub(crate) async fn run_chunk_entry(
76        &mut self,
77        chunk: &Chunk,
78        argc: usize,
79        saved_source_dir: Option<std::path::PathBuf>,
80        module_functions: Option<ModuleFunctionRegistry>,
81        module_state: Option<crate::value::ModuleState>,
82    ) -> Result<VmValue, VmError> {
83        let initial_env = self.env.clone();
84        self.frames.push(CallFrame {
85            chunk: chunk.clone(),
86            ip: 0,
87            stack_base: self.stack.len(),
88            saved_env: self.env.clone(),
89            initial_env: Some(initial_env),
90            saved_iterator_depth: self.iterators.len(),
91            fn_name: String::new(),
92            argc,
93            saved_source_dir,
94            module_functions,
95            module_state,
96        });
97
98        loop {
99            if let Some(&(deadline, _)) = self.deadlines.last() {
100                if Instant::now() > deadline {
101                    self.deadlines.pop();
102                    let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
103                    match self.handle_error(err) {
104                        Ok(None) => continue,
105                        Ok(Some(val)) => return Ok(val),
106                        Err(e) => return Err(e),
107                    }
108                }
109            }
110
111            let frame = match self.frames.last_mut() {
112                Some(f) => f,
113                None => return Ok(self.stack.pop().unwrap_or(VmValue::Nil)),
114            };
115
116            if frame.ip >= frame.chunk.code.len() {
117                let val = self.stack.pop().unwrap_or(VmValue::Nil);
118                let popped_frame = self.frames.pop().unwrap();
119                if let Some(ref dir) = popped_frame.saved_source_dir {
120                    crate::stdlib::set_thread_source_dir(dir);
121                }
122
123                if self.frames.is_empty() {
124                    return Ok(val);
125                } else {
126                    self.iterators.truncate(popped_frame.saved_iterator_depth);
127                    self.env = popped_frame.saved_env;
128                    self.stack.truncate(popped_frame.stack_base);
129                    self.stack.push(val);
130                    continue;
131                }
132            }
133
134            let op = frame.chunk.code[frame.ip];
135            frame.ip += 1;
136
137            match self.execute_op(op).await {
138                Ok(Some(val)) => return Ok(val),
139                Ok(None) => continue,
140                Err(VmError::Return(val)) => {
141                    if let Some(popped_frame) = self.frames.pop() {
142                        if let Some(ref dir) = popped_frame.saved_source_dir {
143                            crate::stdlib::set_thread_source_dir(dir);
144                        }
145                        let current_depth = self.frames.len();
146                        self.exception_handlers
147                            .retain(|h| h.frame_depth <= current_depth);
148
149                        if self.frames.is_empty() {
150                            return Ok(val);
151                        }
152                        self.iterators.truncate(popped_frame.saved_iterator_depth);
153                        self.env = popped_frame.saved_env;
154                        self.stack.truncate(popped_frame.stack_base);
155                        self.stack.push(val);
156                    } else {
157                        return Ok(val);
158                    }
159                }
160                Err(e) => {
161                    // Capture stack trace before error handling unwinds frames.
162                    if self.error_stack_trace.is_empty() {
163                        self.error_stack_trace = self.capture_stack_trace();
164                    }
165                    match self.handle_error(e) {
166                        Ok(None) => {
167                            self.error_stack_trace.clear();
168                            continue;
169                        }
170                        Ok(Some(val)) => return Ok(val),
171                        Err(e) => return Err(self.enrich_error_with_line(e)),
172                    }
173                }
174            }
175        }
176    }
177
178    pub(crate) async fn execute_one_cycle(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
179        if let Some(&(deadline, _)) = self.deadlines.last() {
180            if Instant::now() > deadline {
181                self.deadlines.pop();
182                let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
183                match self.handle_error(err) {
184                    Ok(None) => return Ok(None),
185                    Ok(Some(val)) => return Ok(Some((val, false))),
186                    Err(e) => return Err(e),
187                }
188            }
189        }
190
191        let frame = match self.frames.last_mut() {
192            Some(f) => f,
193            None => {
194                let val = self.stack.pop().unwrap_or(VmValue::Nil);
195                return Ok(Some((val, false)));
196            }
197        };
198
199        if frame.ip >= frame.chunk.code.len() {
200            let val = self.stack.pop().unwrap_or(VmValue::Nil);
201            let popped_frame = self.frames.pop().unwrap();
202            if self.frames.is_empty() {
203                return Ok(Some((val, false)));
204            } else {
205                self.iterators.truncate(popped_frame.saved_iterator_depth);
206                self.env = popped_frame.saved_env;
207                self.stack.truncate(popped_frame.stack_base);
208                self.stack.push(val);
209                return Ok(None);
210            }
211        }
212
213        let op = frame.chunk.code[frame.ip];
214        frame.ip += 1;
215
216        match self.execute_op(op).await {
217            Ok(Some(val)) => Ok(Some((val, false))),
218            Ok(None) => Ok(None),
219            Err(VmError::Return(val)) => {
220                if let Some(popped_frame) = self.frames.pop() {
221                    if let Some(ref dir) = popped_frame.saved_source_dir {
222                        crate::stdlib::set_thread_source_dir(dir);
223                    }
224                    let current_depth = self.frames.len();
225                    self.exception_handlers
226                        .retain(|h| h.frame_depth <= current_depth);
227                    if self.frames.is_empty() {
228                        return Ok(Some((val, false)));
229                    }
230                    self.iterators.truncate(popped_frame.saved_iterator_depth);
231                    self.env = popped_frame.saved_env;
232                    self.stack.truncate(popped_frame.stack_base);
233                    self.stack.push(val);
234                    Ok(None)
235                } else {
236                    Ok(Some((val, false)))
237                }
238            }
239            Err(e) => {
240                if self.error_stack_trace.is_empty() {
241                    self.error_stack_trace = self.capture_stack_trace();
242                }
243                match self.handle_error(e) {
244                    Ok(None) => {
245                        self.error_stack_trace.clear();
246                        Ok(None)
247                    }
248                    Ok(Some(val)) => Ok(Some((val, false))),
249                    Err(e) => Err(self.enrich_error_with_line(e)),
250                }
251            }
252        }
253    }
254
255    /// Capture the current call stack as (fn_name, line, col, source_file) tuples.
256    pub(crate) fn capture_stack_trace(&self) -> Vec<(String, usize, usize, Option<String>)> {
257        self.frames
258            .iter()
259            .map(|f| {
260                let idx = if f.ip > 0 { f.ip - 1 } else { 0 };
261                let line = f.chunk.lines.get(idx).copied().unwrap_or(0) as usize;
262                let col = f.chunk.columns.get(idx).copied().unwrap_or(0) as usize;
263                (f.fn_name.clone(), line, col, f.chunk.source_file.clone())
264            })
265            .collect()
266    }
267
268    /// Enrich a VmError with source line information from the captured stack
269    /// trace. Appends ` (line N)` to error variants whose messages don't
270    /// already carry location context.
271    pub(crate) fn enrich_error_with_line(&self, error: VmError) -> VmError {
272        // Determine the line from the captured stack trace (innermost frame).
273        let line = self
274            .error_stack_trace
275            .last()
276            .map(|(_, l, _, _)| *l)
277            .unwrap_or_else(|| self.current_line());
278        if line == 0 {
279            return error;
280        }
281        let suffix = format!(" (line {line})");
282        match error {
283            VmError::Runtime(msg) => VmError::Runtime(format!("{msg}{suffix}")),
284            VmError::TypeError(msg) => VmError::TypeError(format!("{msg}{suffix}")),
285            VmError::DivisionByZero => VmError::Runtime(format!("Division by zero{suffix}")),
286            VmError::UndefinedVariable(name) => {
287                VmError::Runtime(format!("Undefined variable: {name}{suffix}"))
288            }
289            VmError::UndefinedBuiltin(name) => {
290                VmError::Runtime(format!("Undefined builtin: {name}{suffix}"))
291            }
292            VmError::ImmutableAssignment(name) => VmError::Runtime(format!(
293                "Cannot assign to immutable binding: {name}{suffix}"
294            )),
295            VmError::StackOverflow => {
296                VmError::Runtime(format!("Stack overflow: too many nested calls{suffix}"))
297            }
298            // Leave these untouched:
299            // - Thrown: user-thrown errors should not be silently modified
300            // - CategorizedError: structured errors for agent orchestration
301            // - Return: control flow, not a real error
302            // - StackUnderflow / InvalidInstruction: internal VM bugs
303            other => other,
304        }
305    }
306}