Skip to main content

brainwires_code_interpreters/
executor.rs

1//! Main executor that routes to appropriate language implementation
2
3use crate::types::{ExecutionError, ExecutionLimits, ExecutionRequest, ExecutionResult, Language};
4
5/// Main code executor that dispatches to language-specific implementations
6pub struct Executor {
7    limits: ExecutionLimits,
8}
9
10impl Executor {
11    /// Create a new executor with default limits
12    pub fn new() -> Self {
13        Self {
14            limits: ExecutionLimits::default(),
15        }
16    }
17
18    /// Create a new executor with custom limits
19    pub fn with_limits(limits: ExecutionLimits) -> Self {
20        Self { limits }
21    }
22
23    /// Execute code in the specified language
24    pub fn execute(&self, request: ExecutionRequest) -> ExecutionResult {
25        // Merge limits
26        let request = if request.limits.is_none() {
27            ExecutionRequest {
28                limits: Some(self.limits.clone()),
29                ..request
30            }
31        } else {
32            request
33        };
34
35        // Dispatch to appropriate executor
36        match request.language {
37            #[cfg(feature = "rhai")]
38            Language::Rhai => {
39                use crate::languages::rhai::RhaiExecutor;
40                let executor = RhaiExecutor::with_limits(
41                    request
42                        .limits
43                        .clone()
44                        .unwrap_or_else(ExecutionLimits::default),
45                );
46                executor.execute_code(&request)
47            }
48
49            #[cfg(feature = "lua")]
50            Language::Lua => {
51                use crate::languages::lua::LuaExecutor;
52                let executor = LuaExecutor::with_limits(
53                    request
54                        .limits
55                        .clone()
56                        .unwrap_or_else(ExecutionLimits::default),
57                );
58                executor.execute_code(&request)
59            }
60
61            #[cfg(feature = "javascript")]
62            Language::JavaScript => {
63                use crate::languages::javascript::JavaScriptExecutor;
64                let executor = JavaScriptExecutor::with_limits(
65                    request
66                        .limits
67                        .clone()
68                        .unwrap_or_else(ExecutionLimits::default),
69                );
70                executor.execute_code(&request)
71            }
72
73            #[cfg(feature = "python")]
74            Language::Python => {
75                use crate::languages::python::PythonExecutor;
76                let executor = PythonExecutor::with_limits(
77                    request
78                        .limits
79                        .clone()
80                        .unwrap_or_else(ExecutionLimits::default),
81                );
82                executor.execute_code(&request)
83            }
84
85            #[allow(unreachable_patterns)]
86            _ => ExecutionError::UnsupportedLanguage(request.language.to_string()).to_result(0),
87        }
88    }
89
90    /// Execute code from a string, parsing the language
91    pub fn execute_str(&self, language: &str, code: &str) -> ExecutionResult {
92        match Language::parse(language) {
93            Some(lang) => self.execute(ExecutionRequest {
94                language: lang,
95                code: code.to_string(),
96                limits: Some(self.limits.clone()),
97                ..Default::default()
98            }),
99            None => ExecutionError::UnsupportedLanguage(language.to_string()).to_result(0),
100        }
101    }
102
103    /// Get list of supported languages
104    pub fn supported_languages(&self) -> Vec<Language> {
105        crate::supported_languages()
106    }
107
108    /// Check if a language is supported
109    pub fn is_supported(&self, language: Language) -> bool {
110        crate::is_language_supported(language)
111    }
112
113    /// Get the current limits
114    pub fn limits(&self) -> &ExecutionLimits {
115        &self.limits
116    }
117}
118
119impl Default for Executor {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_executor_creation() {
131        let executor = Executor::new();
132        assert!(!executor.supported_languages().is_empty());
133    }
134
135    #[test]
136    fn test_executor_with_limits() {
137        let limits = ExecutionLimits::strict();
138        let executor = Executor::with_limits(limits.clone());
139        assert_eq!(executor.limits().max_timeout_ms, limits.max_timeout_ms);
140    }
141
142    #[test]
143    #[cfg(feature = "rhai")]
144    fn test_rhai_execution() {
145        let executor = Executor::new();
146        let result = executor.execute_str("rhai", "1 + 2");
147        assert!(result.success);
148        assert!(result.stdout.contains("3"));
149    }
150
151    #[test]
152    #[cfg(feature = "lua")]
153    fn test_lua_execution() {
154        let executor = Executor::new();
155        let result = executor.execute_str("lua", "return 1 + 2");
156        assert!(result.success);
157        assert!(result.stdout.contains("3"));
158    }
159
160    #[test]
161    #[cfg(feature = "javascript")]
162    fn test_javascript_execution() {
163        let executor = Executor::new();
164        let result = executor.execute_str("js", "1 + 2");
165        assert!(result.success);
166        assert!(result.stdout.contains("3"));
167    }
168
169    #[test]
170    #[cfg(feature = "python")]
171    fn test_python_execution() {
172        let executor = Executor::new();
173        let result = executor.execute_str("python", "print(1 + 2)");
174        assert!(result.success);
175        assert!(result.stdout.contains("3"));
176    }
177
178    #[test]
179    fn test_unsupported_language() {
180        let executor = Executor::new();
181        let result = executor.execute_str("cobol", "DISPLAY 'HELLO'");
182        assert!(!result.success);
183        assert!(result.error.unwrap().contains("not supported"));
184    }
185
186    #[test]
187    fn test_language_aliases() {
188        // Test that language parsing works for aliases
189        // (whether the language is actually supported depends on features)
190        assert!(Language::parse("python").is_some());
191        assert!(Language::parse("py").is_some());
192        assert!(Language::parse("javascript").is_some());
193        assert!(Language::parse("js").is_some());
194        assert!(Language::parse("lua").is_some());
195        assert!(Language::parse("rhai").is_some());
196
197        // Ensure both aliases map to the same variant
198        assert_eq!(Language::parse("python"), Language::parse("py"));
199        assert_eq!(Language::parse("javascript"), Language::parse("js"));
200    }
201}