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