1use crate::Result;
7use runmat_builtins::Value;
8use runmat_core::{RunError, RunMatSession};
9use runmat_time::Instant;
10use std::path::Path;
11use std::time::Duration;
12
13pub struct ExecutionEngine {
15 execution_count: u64,
17 timeout: Option<Duration>,
19 debug: bool,
21 repl_engine: RunMatSession,
23}
24
25#[derive(Debug, Clone)]
27pub struct ExecutionResult {
28 pub status: ExecutionStatus,
30 pub stdout: String,
32 pub stderr: String,
34 pub result: Option<Value>,
36 pub execution_time_ms: u64,
38 pub error: Option<ExecutionError>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum ExecutionStatus {
45 Success,
47 Error,
49 Interrupted,
51 Timeout,
53}
54
55#[derive(Debug, Clone)]
57pub struct ExecutionError {
58 pub error_type: String,
60 pub identifier: Option<String>,
62 pub message: String,
64 pub traceback: Vec<String>,
66}
67
68impl ExecutionEngine {
69 pub fn new() -> Self {
71 let repl_engine =
72 RunMatSession::with_options(true, false).expect("Failed to create RunMatSession");
73 Self {
74 execution_count: 0,
75 timeout: Some(Duration::from_secs(300)), debug: false,
77 repl_engine,
78 }
79 }
80
81 pub fn with_timeout(timeout: Option<Duration>) -> Self {
83 let repl_engine =
84 RunMatSession::with_options(true, false).expect("Failed to create RunMatSession");
85 Self {
86 execution_count: 0,
87 timeout,
88 debug: false,
89 repl_engine,
90 }
91 }
92
93 pub fn with_options(enable_jit: bool, debug: bool, timeout: Option<Duration>) -> Result<Self> {
95 Self::with_snapshot(enable_jit, debug, timeout, None::<&str>)
96 }
97
98 pub fn with_snapshot<P: AsRef<Path>>(
100 enable_jit: bool,
101 debug: bool,
102 timeout: Option<Duration>,
103 snapshot_path: Option<P>,
104 ) -> Result<Self> {
105 let repl_engine =
106 RunMatSession::with_snapshot(enable_jit, debug, snapshot_path).map_err(|e| {
107 crate::KernelError::Internal(format!("Failed to create RunMatSession: {e}"))
108 })?;
109 Ok(Self {
110 execution_count: 0,
111 timeout,
112 debug,
113 repl_engine,
114 })
115 }
116
117 pub fn set_debug(&mut self, debug: bool) {
119 self.debug = debug;
120 }
121
122 pub fn execution_count(&self) -> u64 {
124 self.execution_count
125 }
126
127 pub async fn execute(&mut self, code: &str) -> Result<ExecutionResult> {
129 let start_time = Instant::now();
130 self.execution_count += 1;
131
132 if self.debug {
133 log::debug!("Executing code ({}): {}", self.execution_count, code);
134 }
135
136 match self.repl_engine.execute(code).await {
138 Ok(repl_result) => {
139 let execution_time_ms = start_time.elapsed().as_millis() as u64;
140
141 if let Some(error) = repl_result.error {
142 let identifier = error.identifier().map(ToString::to_string);
143 let error_message = error.format_diagnostic();
144 let traceback = if error.context.call_stack.is_empty() {
145 vec!["Error during code execution".to_string()]
146 } else {
147 error.context.call_stack.clone()
148 };
149
150 Ok(ExecutionResult {
151 status: ExecutionStatus::Error,
152 stdout: self.capture_stdout(),
153 stderr: self.capture_stderr(&error_message),
154 result: None,
155 execution_time_ms,
156 error: Some(ExecutionError {
157 error_type: identifier
158 .clone()
159 .unwrap_or_else(|| "RuntimeError".to_string()),
160 identifier,
161 message: error_message,
162 traceback,
163 }),
164 })
165 } else {
166 Ok(ExecutionResult {
167 status: ExecutionStatus::Success,
168 stdout: self.capture_stdout(),
169 stderr: String::new(), result: repl_result.value,
171 execution_time_ms,
172 error: None,
173 })
174 }
175 }
176 Err(e) => {
177 let execution_time_ms = start_time.elapsed().as_millis() as u64;
178 let (error_type, identifier, message) = match e {
179 RunError::Syntax(err) => ("SyntaxError".to_string(), None, err.to_string()),
180 RunError::Semantic(err) => {
181 let identifier = err.identifier.clone();
182 (
183 identifier
184 .clone()
185 .unwrap_or_else(|| "SemanticError".to_string()),
186 identifier,
187 err.to_string(),
188 )
189 }
190 RunError::Compile(err) => {
191 let identifier = err.identifier.clone();
192 (
193 identifier
194 .clone()
195 .unwrap_or_else(|| "CompileError".to_string()),
196 identifier,
197 err.to_string(),
198 )
199 }
200 RunError::Runtime(err) => {
201 let identifier = err.identifier().map(ToString::to_string);
202 (
203 identifier
204 .clone()
205 .unwrap_or_else(|| "RuntimeError".to_string()),
206 identifier,
207 err.format_diagnostic(),
208 )
209 }
210 };
211
212 Ok(ExecutionResult {
213 status: ExecutionStatus::Error,
214 stdout: String::new(),
215 stderr: String::new(),
216 result: None,
217 execution_time_ms,
218 error: Some(ExecutionError {
219 error_type,
220 identifier,
221 message,
222 traceback: vec!["Error during code execution".to_string()],
223 }),
224 })
225 }
226 }
227 }
228
229 pub async fn execute_with_timeout(
231 &mut self,
232 code: &str,
233 timeout: Duration,
234 ) -> Result<ExecutionResult> {
235 let original_timeout = self.timeout;
236 self.timeout = Some(timeout);
237 let result = self.execute(code).await;
238 self.timeout = original_timeout;
239 result
240 }
241
242 pub fn reset(&mut self) {
244 self.execution_count = 0;
245 if self.debug {
246 log::debug!("Execution engine reset");
247 }
248 }
249
250 pub fn stats(&self) -> ExecutionStats {
252 let repl_stats = self.repl_engine.stats();
253 ExecutionStats {
254 execution_count: self.execution_count,
255 timeout_seconds: self.timeout.map(|d| d.as_secs()),
256 debug_enabled: self.debug,
257 repl_total_executions: repl_stats.total_executions,
258 repl_jit_compiled: repl_stats.jit_compiled,
259 repl_interpreter_fallback: repl_stats.interpreter_fallback,
260 repl_average_time_ms: repl_stats.average_execution_time_ms,
261 }
262 }
263
264 pub fn snapshot_info(&self) -> Option<String> {
266 self.repl_engine.snapshot_info()
267 }
268
269 pub fn has_snapshot(&self) -> bool {
271 self.repl_engine.has_snapshot()
272 }
273
274 fn capture_stdout(&self) -> String {
276 String::new()
281 }
282
283 fn capture_stderr(&self, error_msg: &str) -> String {
285 format!("Error: {error_msg}")
287 }
288}
289
290impl Default for ExecutionEngine {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296#[derive(Debug, Clone)]
298pub struct ExecutionStats {
299 pub execution_count: u64,
301 pub timeout_seconds: Option<u64>,
303 pub debug_enabled: bool,
305 pub repl_total_executions: usize,
307 pub repl_jit_compiled: usize,
309 pub repl_interpreter_fallback: usize,
311 pub repl_average_time_ms: f64,
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use futures::executor::block_on;
319
320 #[test]
321 fn test_execution_engine_creation() {
322 let engine = ExecutionEngine::new();
323 assert_eq!(engine.execution_count(), 0);
324 assert!(!engine.debug);
325 }
326
327 #[test]
328 fn test_execution_engine_with_timeout() {
329 let timeout = Duration::from_secs(60);
330 let engine = ExecutionEngine::with_timeout(Some(timeout));
331 assert_eq!(engine.timeout, Some(timeout));
332 }
333
334 #[test]
335 fn test_simple_execution() {
336 let mut engine = ExecutionEngine::new();
337 let result = block_on(engine.execute("x = 1 + 2")).unwrap();
338
339 assert_eq!(result.status, ExecutionStatus::Success);
340 assert_eq!(engine.execution_count(), 1);
341 let _time = result.execution_time_ms;
343 assert!(result.error.is_none());
344 }
345
346 #[test]
347 fn test_parse_error_handling() {
348 let mut engine = ExecutionEngine::new();
349 let result = block_on(engine.execute("x = 1 +")).unwrap();
350
351 assert_eq!(result.status, ExecutionStatus::Error);
352 assert!(result.error.is_some());
353
354 let error = result.error.unwrap();
355 assert_eq!(error.error_type, "SyntaxError");
356 assert!(!error.message.is_empty());
357 }
358
359 #[test]
360 fn test_runtime_error_handling() {
361 let mut engine = ExecutionEngine::new();
362 let result = block_on(engine.execute("x = undefined_var")).unwrap();
363
364 assert_eq!(result.status, ExecutionStatus::Error);
365 assert!(result.error.is_some());
366
367 let error = result.error.unwrap();
368 assert!(
369 error.error_type == "RuntimeError"
370 || error.error_type == "CompileError"
371 || error.error_type == "UndefinedVariable"
372 || error.error_type == "SemanticError"
373 || error.error_type.contains(':')
374 );
375 }
376
377 #[test]
378 fn test_execution_count_increment() {
379 let mut engine = ExecutionEngine::new();
380
381 block_on(engine.execute("x = 1")).unwrap();
382 assert_eq!(engine.execution_count(), 1);
383
384 block_on(engine.execute("y = 2")).unwrap();
385 assert_eq!(engine.execution_count(), 2);
386
387 block_on(engine.execute("invalid syntax")).unwrap();
389 assert_eq!(engine.execution_count(), 3);
390 }
391
392 #[test]
393 fn test_engine_reset() {
394 let mut engine = ExecutionEngine::new();
395 block_on(engine.execute("x = 1")).unwrap();
396 assert_eq!(engine.execution_count(), 1);
397
398 engine.reset();
399 assert_eq!(engine.execution_count(), 0);
400 }
401
402 #[test]
403 fn test_debug_mode() {
404 let mut engine = ExecutionEngine::new();
405 assert!(!engine.debug);
406
407 engine.set_debug(true);
408 assert!(engine.debug);
409
410 engine.set_debug(false);
411 assert!(!engine.debug);
412 }
413
414 #[test]
415 fn test_stats() {
416 let mut engine = ExecutionEngine::new();
417 engine.set_debug(true);
418 block_on(engine.execute("x = 1")).unwrap();
419
420 let stats = engine.stats();
421 assert_eq!(stats.execution_count, 1);
422 assert!(stats.debug_enabled);
423 assert!(stats.timeout_seconds.is_some());
424 }
425}