Skip to main content

runmat_kernel/
execution.rs

1//! Execution engine for RunMat code within the Jupyter kernel
2//!
3//! Provides a kernel-specific wrapper around the RunMatSession to adapt
4//! its interface for Jupyter protocol requirements.
5
6use crate::Result;
7use runmat_builtins::Value;
8use runmat_core::{RunError, RunMatSession};
9use runmat_time::Instant;
10use std::path::Path;
11use std::time::Duration;
12
13/// Execution engine managing RunMat code execution state for the Jupyter kernel
14pub struct ExecutionEngine {
15    /// Current execution counter
16    execution_count: u64,
17    /// Execution timeout
18    timeout: Option<Duration>,
19    /// Whether debug mode is enabled
20    debug: bool,
21    /// Underlying REPL engine that does the actual execution
22    repl_engine: RunMatSession,
23}
24
25/// Result of code execution
26#[derive(Debug, Clone)]
27pub struct ExecutionResult {
28    /// Execution status
29    pub status: ExecutionStatus,
30    /// Standard output captured during execution
31    pub stdout: String,
32    /// Standard error captured during execution  
33    pub stderr: String,
34    /// Execution result value (if successful)
35    pub result: Option<Value>,
36    /// Execution time in milliseconds
37    pub execution_time_ms: u64,
38    /// Any error that occurred
39    pub error: Option<ExecutionError>,
40}
41
42/// Execution status
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum ExecutionStatus {
45    /// Execution completed successfully
46    Success,
47    /// Execution failed with an error
48    Error,
49    /// Execution was interrupted/cancelled
50    Interrupted,
51    /// Execution timed out
52    Timeout,
53}
54
55/// Execution error details
56#[derive(Debug, Clone)]
57pub struct ExecutionError {
58    /// Error type/name
59    pub error_type: String,
60    /// Stable identifier code when available
61    pub identifier: Option<String>,
62    /// Error message
63    pub message: String,
64    /// Error traceback/stack trace
65    pub traceback: Vec<String>,
66}
67
68impl ExecutionEngine {
69    /// Create a new execution engine
70    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)), // 5 minutes default
76            debug: false,
77            repl_engine,
78        }
79    }
80
81    /// Create a new execution engine with custom timeout
82    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    /// Create a new execution engine with specific options
94    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    /// Create a new execution engine with snapshot support
99    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    /// Enable or disable debug mode
118    pub fn set_debug(&mut self, debug: bool) {
119        self.debug = debug;
120    }
121
122    /// Get current execution count
123    pub fn execution_count(&self) -> u64 {
124        self.execution_count
125    }
126
127    /// Execute code
128    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        // Execute using the underlying RunMatSession
137        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(), // No errors on success
170                        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    /// Execute code with a specific timeout
230    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    /// Reset the execution engine state
243    pub fn reset(&mut self) {
244        self.execution_count = 0;
245        if self.debug {
246            log::debug!("Execution engine reset");
247        }
248    }
249
250    /// Get engine statistics
251    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    /// Get snapshot information
265    pub fn snapshot_info(&self) -> Option<String> {
266        self.repl_engine.snapshot_info()
267    }
268
269    /// Check if a snapshot is loaded
270    pub fn has_snapshot(&self) -> bool {
271        self.repl_engine.has_snapshot()
272    }
273
274    /// Capture stdout output from the REPL execution
275    fn capture_stdout(&self) -> String {
276        // In a production implementation, this would capture actual stdout
277        // For now, we simulate by checking if there were any successful results
278        // The REPL itself doesn't currently emit to stdout, but this would be the place
279        // to capture print() function output or similar
280        String::new()
281    }
282
283    /// Capture stderr output, including error messages
284    fn capture_stderr(&self, error_msg: &str) -> String {
285        // Format error message for stderr
286        format!("Error: {error_msg}")
287    }
288}
289
290impl Default for ExecutionEngine {
291    fn default() -> Self {
292        Self::new()
293    }
294}
295
296/// Execution engine statistics
297#[derive(Debug, Clone)]
298pub struct ExecutionStats {
299    /// Total number of executions performed by the kernel
300    pub execution_count: u64,
301    /// Configured timeout in seconds (if any)
302    pub timeout_seconds: Option<u64>,
303    /// Whether debug mode is enabled
304    pub debug_enabled: bool,
305    /// Total executions performed by the underlying REPL engine
306    pub repl_total_executions: usize,
307    /// Number of JIT compiled executions
308    pub repl_jit_compiled: usize,
309    /// Number of interpreter fallback executions
310    pub repl_interpreter_fallback: usize,
311    /// Average execution time in milliseconds
312    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        // Just verify that execution_time_ms field exists and is accessible
342        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        // Even failed executions increment the counter
388        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}