1use super::ValueWord;
4
5pub struct VMContext<'vm> {
7 pub stack: &'vm mut Vec<ValueWord>,
9 pub locals: &'vm mut Vec<ValueWord>,
11 pub globals: &'vm mut Vec<ValueWord>,
13}
14
15#[derive(Debug, Clone, PartialEq, Default)]
17pub struct ErrorLocation {
18 pub line: usize,
20 pub column: usize,
22 pub file: Option<String>,
24 pub source_line: Option<String>,
26}
27
28impl ErrorLocation {
29 pub fn new(line: usize, column: usize) -> Self {
30 Self {
31 line,
32 column,
33 file: None,
34 source_line: None,
35 }
36 }
37
38 pub fn with_file(mut self, file: impl Into<String>) -> Self {
39 self.file = Some(file.into());
40 self
41 }
42
43 pub fn with_source_line(mut self, source: impl Into<String>) -> Self {
44 self.source_line = Some(source.into());
45 self
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, thiserror::Error)]
51pub enum VMError {
52 #[error("Stack underflow")]
54 StackUnderflow,
55 #[error("Stack overflow")]
57 StackOverflow,
58 #[error("Type error: expected {expected}, got {got}")]
60 TypeError {
61 expected: &'static str,
62 got: &'static str,
63 },
64 #[error("Division by zero")]
66 DivisionByZero,
67 #[error("Undefined variable: {0}")]
69 UndefinedVariable(String),
70 #[error("Undefined property: {0}")]
72 UndefinedProperty(String),
73 #[error("Invalid function call")]
75 InvalidCall,
76 #[error("Index out of bounds: {index} (length: {length})")]
78 IndexOutOfBounds { index: i32, length: usize },
79 #[error("Invalid operand")]
81 InvalidOperand,
82 #[error("{function}() expects {expected} argument(s), got {got}")]
84 ArityMismatch {
85 function: String,
86 expected: usize,
87 got: usize,
88 },
89 #[error("{function}(): {message}")]
91 InvalidArgument { function: String, message: String },
92 #[error("Not implemented: {0}")]
94 NotImplemented(String),
95 #[error("{0}")]
97 RuntimeError(String),
98 #[error("Suspended on future {future_id}")]
100 Suspended { future_id: u64, resume_ip: usize },
101 #[error("Execution interrupted")]
103 Interrupted,
104 #[error("Resume requested")]
107 ResumeRequested,
108}
109
110impl VMError {
111 #[inline]
113 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
114 Self::TypeError { expected, got }
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct LocatedVMError {
121 pub error: VMError,
122 pub location: Option<ErrorLocation>,
123}
124
125impl LocatedVMError {
126 pub fn new(error: VMError) -> Self {
127 Self {
128 error,
129 location: None,
130 }
131 }
132
133 pub fn with_location(error: VMError, location: ErrorLocation) -> Self {
134 Self {
135 error,
136 location: Some(location),
137 }
138 }
139}
140
141impl std::fmt::Display for LocatedVMError {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 if let Some(loc) = &self.location {
145 if let Some(file) = &loc.file {
147 writeln!(f, "error: {}", self.error)?;
148 writeln!(f, " --> {}:{}:{}", file, loc.line, loc.column)?;
149 } else {
150 writeln!(f, "error: {}", self.error)?;
151 writeln!(f, " --> line {}:{}", loc.line, loc.column)?;
152 }
153
154 if let Some(source) = &loc.source_line {
156 writeln!(f, " |")?;
157 writeln!(f, "{:>3} | {}", loc.line, source)?;
158 let padding = " ".repeat(loc.column.saturating_sub(1));
160 writeln!(f, " | {}^", padding)?;
161 }
162 Ok(())
163 } else {
164 write!(f, "error: {}", self.error)
165 }
166 }
167}
168
169impl std::error::Error for LocatedVMError {}
170
171impl From<shape_ast::error::ShapeError> for VMError {
172 fn from(err: shape_ast::error::ShapeError) -> Self {
173 VMError::RuntimeError(err.to_string())
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_runtime_error_no_double_prefix() {
183 let err = VMError::RuntimeError("something went wrong".to_string());
184 let display = format!("{}", err);
185 assert_eq!(display, "something went wrong");
187 assert!(!display.contains("Runtime error:"));
188 }
189
190 #[test]
191 fn test_located_error_formatting() {
192 let err = LocatedVMError::with_location(
193 VMError::RuntimeError("bad op".to_string()),
194 ErrorLocation::new(5, 3).with_source_line("let x = 1 + \"a\""),
195 );
196 let display = format!("{}", err);
197 assert!(display.contains("bad op"));
198 assert!(display.contains("line 5"));
199 assert!(display.contains("let x = 1 + \"a\""));
200 }
201}