brainwires_code_interpreters/languages/
rhai.rs1use rhai::{Dynamic, Engine, EvalAltResult, Scope};
16use std::sync::{Arc, Mutex};
17use std::time::Instant;
18
19use super::{LanguageExecutor, get_limits, truncate_output};
20use crate::types::{ExecutionLimits, ExecutionRequest, ExecutionResult};
21
22pub struct RhaiExecutor {
24 _limits: ExecutionLimits,
25}
26
27impl RhaiExecutor {
28 pub fn new() -> Self {
30 Self {
31 _limits: ExecutionLimits::default(),
32 }
33 }
34
35 pub fn with_limits(limits: ExecutionLimits) -> Self {
37 Self { _limits: limits }
38 }
39
40 fn configure_engine(&self, limits: &ExecutionLimits) -> Engine {
42 let mut engine = Engine::new();
43
44 engine.set_max_operations(limits.max_operations);
46 engine.set_max_string_size(limits.max_string_length);
47 engine.set_max_array_size(limits.max_array_length);
48 engine.set_max_map_size(limits.max_map_size);
49 engine.set_max_expr_depths(
50 limits.max_call_depth as usize,
51 limits.max_call_depth as usize,
52 );
53
54 engine.set_allow_looping(true); engine.set_strict_variables(true); engine
59 }
60
61 fn inject_context(&self, scope: &mut Scope, context: &serde_json::Value) {
63 if let serde_json::Value::Object(map) = context {
64 for (key, value) in map {
65 let dynamic_value = json_to_dynamic(value);
66 scope.push(key.clone(), dynamic_value);
67 }
68 }
69 }
70
71 pub fn execute_code(&self, request: &ExecutionRequest) -> ExecutionResult {
73 let limits = get_limits(request);
74 let engine = self.configure_engine(&limits);
75
76 let start = Instant::now();
77
78 let mut scope = Scope::new();
80 if let Some(context) = &request.context {
81 self.inject_context(&mut scope, context);
82 }
83
84 let output = Arc::new(Mutex::new(Vec::<String>::new()));
86 let output_clone = output.clone();
87
88 let mut engine = engine;
90 engine.on_print(move |s| {
91 if let Ok(mut out) = output_clone.lock() {
92 out.push(s.to_string());
93 }
94 });
95
96 let output_clone2 = output.clone();
97 engine.on_debug(move |s, _src, _pos| {
98 if let Ok(mut out) = output_clone2.lock() {
99 out.push(format!("[DEBUG] {}", s));
100 }
101 });
102
103 let result: Result<Dynamic, Box<EvalAltResult>> =
105 engine.eval_with_scope(&mut scope, &request.code);
106 let timing_ms = start.elapsed().as_millis() as u64;
107
108 let stdout = output.lock().map(|out| out.join("\n")).unwrap_or_default();
110 let stdout = truncate_output(&stdout, limits.max_output_bytes);
111
112 match result {
113 Ok(value) => {
114 let result_value = dynamic_to_json(&value);
115 let mut stdout_with_result = stdout;
116
117 if !value.is_unit() {
119 if !stdout_with_result.is_empty() {
120 stdout_with_result.push('\n');
121 }
122 stdout_with_result.push_str(&format!("{}", value));
123 }
124
125 ExecutionResult {
126 success: true,
127 stdout: stdout_with_result,
128 stderr: String::new(),
129 result: result_value,
130 error: None,
131 timing_ms,
132 memory_used_bytes: None,
133 operations_count: None,
134 }
135 }
136 Err(e) => {
137 let error_message = format_rhai_error(&e);
138 ExecutionResult {
139 success: false,
140 stdout,
141 stderr: error_message.clone(),
142 result: None,
143 error: Some(error_message),
144 timing_ms,
145 memory_used_bytes: None,
146 operations_count: None,
147 }
148 }
149 }
150 }
151}
152
153impl Default for RhaiExecutor {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159impl LanguageExecutor for RhaiExecutor {
160 fn execute(&self, request: &ExecutionRequest) -> ExecutionResult {
161 self.execute_code(request)
162 }
163
164 fn language_name(&self) -> &'static str {
165 "rhai"
166 }
167
168 fn language_version(&self) -> String {
169 "1.20".to_string()
171 }
172}
173
174fn json_to_dynamic(value: &serde_json::Value) -> Dynamic {
176 match value {
177 serde_json::Value::Null => Dynamic::UNIT,
178 serde_json::Value::Bool(b) => Dynamic::from(*b),
179 serde_json::Value::Number(n) => {
180 if let Some(i) = n.as_i64() {
181 Dynamic::from(i)
182 } else if let Some(f) = n.as_f64() {
183 Dynamic::from(f)
184 } else {
185 Dynamic::UNIT
186 }
187 }
188 serde_json::Value::String(s) => Dynamic::from(s.clone()),
189 serde_json::Value::Array(arr) => {
190 let vec: Vec<Dynamic> = arr.iter().map(json_to_dynamic).collect();
191 Dynamic::from(vec)
192 }
193 serde_json::Value::Object(obj) => {
194 let mut map = rhai::Map::new();
195 for (k, v) in obj {
196 map.insert(k.clone().into(), json_to_dynamic(v));
197 }
198 Dynamic::from(map)
199 }
200 }
201}
202
203fn dynamic_to_json(value: &Dynamic) -> Option<serde_json::Value> {
205 if value.is_unit() {
206 return None;
207 }
208
209 if value.is_bool() {
210 return Some(serde_json::Value::Bool(value.as_bool().unwrap_or(false)));
211 }
212
213 if value.is_int() {
214 return Some(serde_json::Value::Number(serde_json::Number::from(
215 value.as_int().unwrap_or(0),
216 )));
217 }
218
219 if value.is_float() {
220 if let Ok(f) = value.as_float()
221 && let Some(n) = serde_json::Number::from_f64(f)
222 {
223 return Some(serde_json::Value::Number(n));
224 }
225 return None;
226 }
227
228 if value.is_string() {
229 return Some(serde_json::Value::String(
230 value.clone().into_string().unwrap_or_default(),
231 ));
232 }
233
234 if value.is_array()
235 && let Ok(arr) = value.clone().into_array()
236 {
237 let json_arr: Vec<serde_json::Value> = arr
238 .into_iter()
239 .filter_map(|v| dynamic_to_json(&v))
240 .collect();
241 return Some(serde_json::Value::Array(json_arr));
242 }
243
244 if value.is_map()
245 && let Some(map) = value.clone().try_cast::<rhai::Map>()
246 {
247 let mut json_map = serde_json::Map::new();
248 for (k, v) in map {
249 if let Some(json_v) = dynamic_to_json(&v) {
250 json_map.insert(k.to_string(), json_v);
251 }
252 }
253 return Some(serde_json::Value::Object(json_map));
254 }
255
256 Some(serde_json::Value::String(format!("{}", value)))
258}
259
260fn format_rhai_error(error: &EvalAltResult) -> String {
262 match error {
263 EvalAltResult::ErrorTooManyOperations(_) => {
264 "Operation limit exceeded - possible infinite loop".to_string()
265 }
266 EvalAltResult::ErrorDataTooLarge(msg, _) => {
267 format!("Data too large: {}", msg)
268 }
269 EvalAltResult::ErrorStackOverflow(_) => {
270 "Stack overflow - too many nested calls".to_string()
271 }
272 EvalAltResult::ErrorParsing(parse_error, _) => {
273 format!("Syntax error: {}", parse_error)
274 }
275 EvalAltResult::ErrorVariableNotFound(name, _) => {
276 format!("Variable not found: {}", name)
277 }
278 EvalAltResult::ErrorFunctionNotFound(name, _) => {
279 format!("Function not found: {}", name)
280 }
281 _ => format!("{}", error),
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::types::Language;
289
290 fn make_request(code: &str) -> ExecutionRequest {
291 ExecutionRequest {
292 language: Language::Rhai,
293 code: code.to_string(),
294 ..Default::default()
295 }
296 }
297
298 #[test]
299 fn test_simple_expression() {
300 let executor = RhaiExecutor::new();
301 let result = executor.execute(&make_request("1 + 2"));
302 assert!(result.success);
303 assert!(result.stdout.contains("3"));
304 }
305
306 #[test]
307 fn test_string_expression() {
308 let executor = RhaiExecutor::new();
309 let result = executor.execute(&make_request(r#""Hello, World!""#));
310 assert!(result.success);
311 assert!(result.stdout.contains("Hello, World!"));
312 }
313
314 #[test]
315 fn test_variable_declaration() {
316 let executor = RhaiExecutor::new();
317 let result = executor.execute(&make_request(
318 r#"
319 let x = 10;
320 let y = 20;
321 x + y
322 "#,
323 ));
324 assert!(result.success);
325 assert!(result.stdout.contains("30"));
326 }
327
328 #[test]
329 fn test_loop() {
330 let executor = RhaiExecutor::new();
331 let result = executor.execute(&make_request(
332 r#"
333 let sum = 0;
334 for i in 0..10 {
335 sum += i;
336 }
337 sum
338 "#,
339 ));
340 assert!(result.success);
341 assert!(result.stdout.contains("45")); }
343
344 #[test]
345 fn test_syntax_error() {
346 let executor = RhaiExecutor::new();
347 let result = executor.execute(&make_request("let x = "));
348 assert!(!result.success);
349 assert!(result.error.is_some());
350 }
351
352 #[test]
353 fn test_undefined_variable() {
354 let executor = RhaiExecutor::new();
355 let result = executor.execute(&make_request("undefined_var"));
356 assert!(!result.success);
357 let err = result.error.unwrap();
358 assert!(
359 err.contains("not found") || err.contains("Undefined"),
360 "Unexpected error: {}",
361 err
362 );
363 }
364
365 #[test]
366 fn test_context_injection() {
367 let executor = RhaiExecutor::new();
368 let mut request = make_request("x + y");
369 request.context = Some(serde_json::json!({
370 "x": 10,
371 "y": 20
372 }));
373 let result = executor.execute(&request);
374 assert!(result.success);
375 assert!(result.stdout.contains("30"));
376 }
377
378 #[test]
379 fn test_array_operations() {
380 let executor = RhaiExecutor::new();
381 let result = executor.execute(&make_request(
382 r#"
383 let arr = [1, 2, 3, 4, 5];
384 arr.len()
385 "#,
386 ));
387 assert!(result.success);
388 assert!(result.stdout.contains("5"));
389 }
390
391 #[test]
392 fn test_map_operations() {
393 let executor = RhaiExecutor::new();
394 let result = executor.execute(&make_request(
395 r#"
396 let map = #{
397 name: "test",
398 value: 42
399 };
400 map.value
401 "#,
402 ));
403 assert!(result.success);
404 assert!(result.stdout.contains("42"));
405 }
406}