use crate::Result;
use runmat_builtins::Value;
use runmat_core::{RunError, RunMatSession};
use runmat_time::Instant;
use std::path::Path;
use std::time::Duration;
pub struct ExecutionEngine {
execution_count: u64,
timeout: Option<Duration>,
debug: bool,
repl_engine: RunMatSession,
}
#[derive(Debug, Clone)]
pub struct ExecutionResult {
pub status: ExecutionStatus,
pub stdout: String,
pub stderr: String,
pub result: Option<Value>,
pub execution_time_ms: u64,
pub error: Option<ExecutionError>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecutionStatus {
Success,
Error,
Interrupted,
Timeout,
}
#[derive(Debug, Clone)]
pub struct ExecutionError {
pub error_type: String,
pub identifier: Option<String>,
pub message: String,
pub traceback: Vec<String>,
}
impl ExecutionEngine {
pub fn new() -> Self {
let repl_engine =
RunMatSession::with_options(true, false).expect("Failed to create RunMatSession");
Self {
execution_count: 0,
timeout: Some(Duration::from_secs(300)), debug: false,
repl_engine,
}
}
pub fn with_timeout(timeout: Option<Duration>) -> Self {
let repl_engine =
RunMatSession::with_options(true, false).expect("Failed to create RunMatSession");
Self {
execution_count: 0,
timeout,
debug: false,
repl_engine,
}
}
pub fn with_options(enable_jit: bool, debug: bool, timeout: Option<Duration>) -> Result<Self> {
Self::with_snapshot(enable_jit, debug, timeout, None::<&str>)
}
pub fn with_snapshot<P: AsRef<Path>>(
enable_jit: bool,
debug: bool,
timeout: Option<Duration>,
snapshot_path: Option<P>,
) -> Result<Self> {
let repl_engine =
RunMatSession::with_snapshot(enable_jit, debug, snapshot_path).map_err(|e| {
crate::KernelError::Internal(format!("Failed to create RunMatSession: {e}"))
})?;
Ok(Self {
execution_count: 0,
timeout,
debug,
repl_engine,
})
}
pub fn set_debug(&mut self, debug: bool) {
self.debug = debug;
}
pub fn execution_count(&self) -> u64 {
self.execution_count
}
pub async fn execute(&mut self, code: &str) -> Result<ExecutionResult> {
let start_time = Instant::now();
self.execution_count += 1;
if self.debug {
log::debug!("Executing code ({}): {}", self.execution_count, code);
}
match self.repl_engine.execute(code).await {
Ok(repl_result) => {
let execution_time_ms = start_time.elapsed().as_millis() as u64;
if let Some(error) = repl_result.error {
let identifier = error.identifier().map(ToString::to_string);
let error_message = error.format_diagnostic();
let traceback = if error.context.call_stack.is_empty() {
vec!["Error during code execution".to_string()]
} else {
error.context.call_stack.clone()
};
Ok(ExecutionResult {
status: ExecutionStatus::Error,
stdout: self.capture_stdout(),
stderr: self.capture_stderr(&error_message),
result: None,
execution_time_ms,
error: Some(ExecutionError {
error_type: identifier
.clone()
.unwrap_or_else(|| "RuntimeError".to_string()),
identifier,
message: error_message,
traceback,
}),
})
} else {
Ok(ExecutionResult {
status: ExecutionStatus::Success,
stdout: self.capture_stdout(),
stderr: String::new(), result: repl_result.value,
execution_time_ms,
error: None,
})
}
}
Err(e) => {
let execution_time_ms = start_time.elapsed().as_millis() as u64;
let (error_type, identifier, message) = match e {
RunError::Syntax(err) => ("SyntaxError".to_string(), None, err.to_string()),
RunError::Semantic(err) => {
let identifier = err.identifier.clone();
(
identifier
.clone()
.unwrap_or_else(|| "SemanticError".to_string()),
identifier,
err.to_string(),
)
}
RunError::Compile(err) => {
let identifier = err.identifier.clone();
(
identifier
.clone()
.unwrap_or_else(|| "CompileError".to_string()),
identifier,
err.to_string(),
)
}
RunError::Runtime(err) => {
let identifier = err.identifier().map(ToString::to_string);
(
identifier
.clone()
.unwrap_or_else(|| "RuntimeError".to_string()),
identifier,
err.format_diagnostic(),
)
}
};
Ok(ExecutionResult {
status: ExecutionStatus::Error,
stdout: String::new(),
stderr: String::new(),
result: None,
execution_time_ms,
error: Some(ExecutionError {
error_type,
identifier,
message,
traceback: vec!["Error during code execution".to_string()],
}),
})
}
}
}
pub async fn execute_with_timeout(
&mut self,
code: &str,
timeout: Duration,
) -> Result<ExecutionResult> {
let original_timeout = self.timeout;
self.timeout = Some(timeout);
let result = self.execute(code).await;
self.timeout = original_timeout;
result
}
pub fn reset(&mut self) {
self.execution_count = 0;
if self.debug {
log::debug!("Execution engine reset");
}
}
pub fn stats(&self) -> ExecutionStats {
let repl_stats = self.repl_engine.stats();
ExecutionStats {
execution_count: self.execution_count,
timeout_seconds: self.timeout.map(|d| d.as_secs()),
debug_enabled: self.debug,
repl_total_executions: repl_stats.total_executions,
repl_jit_compiled: repl_stats.jit_compiled,
repl_interpreter_fallback: repl_stats.interpreter_fallback,
repl_average_time_ms: repl_stats.average_execution_time_ms,
}
}
pub fn snapshot_info(&self) -> Option<String> {
self.repl_engine.snapshot_info()
}
pub fn has_snapshot(&self) -> bool {
self.repl_engine.has_snapshot()
}
fn capture_stdout(&self) -> String {
String::new()
}
fn capture_stderr(&self, error_msg: &str) -> String {
format!("Error: {error_msg}")
}
}
impl Default for ExecutionEngine {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ExecutionStats {
pub execution_count: u64,
pub timeout_seconds: Option<u64>,
pub debug_enabled: bool,
pub repl_total_executions: usize,
pub repl_jit_compiled: usize,
pub repl_interpreter_fallback: usize,
pub repl_average_time_ms: f64,
}
#[cfg(test)]
mod tests {
use super::*;
use futures::executor::block_on;
#[test]
fn test_execution_engine_creation() {
let engine = ExecutionEngine::new();
assert_eq!(engine.execution_count(), 0);
assert!(!engine.debug);
}
#[test]
fn test_execution_engine_with_timeout() {
let timeout = Duration::from_secs(60);
let engine = ExecutionEngine::with_timeout(Some(timeout));
assert_eq!(engine.timeout, Some(timeout));
}
#[test]
fn test_simple_execution() {
let mut engine = ExecutionEngine::new();
let result = block_on(engine.execute("x = 1 + 2")).unwrap();
assert_eq!(result.status, ExecutionStatus::Success);
assert_eq!(engine.execution_count(), 1);
let _time = result.execution_time_ms;
assert!(result.error.is_none());
}
#[test]
fn test_parse_error_handling() {
let mut engine = ExecutionEngine::new();
let result = block_on(engine.execute("x = 1 +")).unwrap();
assert_eq!(result.status, ExecutionStatus::Error);
assert!(result.error.is_some());
let error = result.error.unwrap();
assert_eq!(error.error_type, "SyntaxError");
assert!(!error.message.is_empty());
}
#[test]
fn test_runtime_error_handling() {
let mut engine = ExecutionEngine::new();
let result = block_on(engine.execute("x = undefined_var")).unwrap();
assert_eq!(result.status, ExecutionStatus::Error);
assert!(result.error.is_some());
let error = result.error.unwrap();
assert!(
error.error_type == "RuntimeError"
|| error.error_type == "CompileError"
|| error.error_type == "UndefinedVariable"
|| error.error_type == "SemanticError"
|| error.error_type.contains(':')
);
}
#[test]
fn test_execution_count_increment() {
let mut engine = ExecutionEngine::new();
block_on(engine.execute("x = 1")).unwrap();
assert_eq!(engine.execution_count(), 1);
block_on(engine.execute("y = 2")).unwrap();
assert_eq!(engine.execution_count(), 2);
block_on(engine.execute("invalid syntax")).unwrap();
assert_eq!(engine.execution_count(), 3);
}
#[test]
fn test_engine_reset() {
let mut engine = ExecutionEngine::new();
block_on(engine.execute("x = 1")).unwrap();
assert_eq!(engine.execution_count(), 1);
engine.reset();
assert_eq!(engine.execution_count(), 0);
}
#[test]
fn test_debug_mode() {
let mut engine = ExecutionEngine::new();
assert!(!engine.debug);
engine.set_debug(true);
assert!(engine.debug);
engine.set_debug(false);
assert!(!engine.debug);
}
#[test]
fn test_stats() {
let mut engine = ExecutionEngine::new();
engine.set_debug(true);
block_on(engine.execute("x = 1")).unwrap();
let stats = engine.stats();
assert_eq!(stats.execution_count, 1);
assert!(stats.debug_enabled);
assert!(stats.timeout_seconds.is_some());
}
}