1use crate::errors::InterpreterError;
2use crate::tools::AnyTool;
3use anyhow::Result;
4use pyo3::prelude::*;
5use pyo3::types::{PyDict, PyModule, PyTuple};
6use rustpython_parser::{
7 ast::{
8 self,
9 bigint::{BigInt, Sign},
10 Constant, Expr, Operator, Stmt, UnaryOp,
11 },
12 Parse,
13};
14use serde_json::{self, json};
15use std::{any::Any, collections::HashMap};
16
17pub fn get_base_python_tools() -> HashMap<&'static str, &'static str> {
18 [
19 ("print", "custom_print"),
20 ("isinstance", "isinstance"),
21 ("range", "range"),
22 ("float", "float"),
23 ("int", "int"),
24 ("bool", "bool"),
25 ("str", "str"),
26 ("set", "set"),
27 ("list", "list"),
28 ("dict", "dict"),
29 ("tuple", "tuple"),
30 ("round", "round"),
31 ("ceil", "math.ceil"),
32 ("floor", "math.floor"),
33 ("log", "math.log"),
34 ("exp", "math.exp"),
35 ("sin", "math.sin"),
36 ("cos", "math.cos"),
37 ("tan", "math.tan"),
38 ("asin", "math.asin"),
39 ("acos", "math.acos"),
40 ("atan", "math.atan"),
41 ("atan2", "math.atan2"),
42 ("degrees", "math.degrees"),
43 ("radians", "math.radians"),
44 ("pow", "math.pow"),
45 ("sqrt", "math.sqrt"),
46 ("len", "len"),
47 ("sum", "sum"),
48 ("max", "max"),
49 ("min", "min"),
50 ("abs", "abs"),
51 ("enumerate", "enumerate"),
52 ("zip", "zip"),
53 ("reversed", "reversed"),
54 ("sorted", "sorted"),
55 ("all", "all"),
56 ("any", "any"),
57 ("map", "map"),
58 ("filter", "filter"),
59 ("ord", "ord"),
60 ("chr", "chr"),
61 ("next", "next"),
62 ("iter", "iter"),
63 ("divmod", "divmod"),
64 ("callable", "callable"),
65 ("getattr", "getattr"),
66 ("hasattr", "hasattr"),
67 ("setattr", "setattr"),
68 ("issubclass", "issubclass"),
69 ("type", "type"),
70 ("complex", "complex"),
71 ]
72 .iter()
73 .cloned()
74 .collect()
75}
76
77
78
79impl From<PyErr> for InterpreterError {
80 fn from(err: PyErr) -> Self {
81 InterpreterError::RuntimeError(err.to_string())
82 }
83}
84
85#[derive(Clone, Debug)]
86pub enum CustomConstant {
87 Int(BigInt),
88 Float(f64),
89 Str(String),
90 Bool(bool),
91 Tuple(Vec<CustomConstant>),
92 PyObj(PyObject),
93 Dict(Vec<String>, Vec<CustomConstant>),
94}
95
96impl CustomConstant {
97 pub fn float(&self) -> Option<f64> {
98 match self {
99 CustomConstant::Float(f) => Some(*f),
100 _ => None,
101 }
102 }
103 pub fn str(&self) -> String {
104 match self {
105 CustomConstant::Str(s) => s.clone(),
106 CustomConstant::Float(f) => f.to_string(),
107 CustomConstant::Int(i) => i.to_string(),
108 CustomConstant::Tuple(t) => {
109 let mut result = String::new();
110 result.push('[');
111 for (i, item) in t.iter().enumerate() {
112 if i > 0 {
113 result.push_str(", ");
114 }
115 result.push_str(&item.str());
116 }
117 result.push(']');
118 result
119 }
120 CustomConstant::Dict(keys, values) => {
121 let mut result = String::new();
122 result.push('{');
123 for (i, key) in keys.iter().enumerate() {
124 if i > 0 {
125 result.push_str(", ");
126 }
127 result.push_str(&format!("'{}': {}", key, values[i].str()));
128 }
129 result.push('}');
130
131 for (i, item) in values.iter().enumerate() {
132 if i > 0 {
133 result.push_str(", ");
134 }
135 result.push_str(&item.str());
136 }
137 result.push('}');
138 result
139 }
140 CustomConstant::PyObj(obj) => obj.to_string(),
141 CustomConstant::Bool(b) => b.to_string(),
142 }
143 }
144 pub fn tuple(&self) -> Option<Vec<CustomConstant>> {
145 match self {
146 CustomConstant::Tuple(t) => Some(t.clone()),
147 _ => None,
148 }
149 }
150}
151
152impl From<CustomConstant> for Constant {
153 fn from(custom: CustomConstant) -> Self {
154 match custom {
155 CustomConstant::Int(i) => Constant::Int(i),
156 CustomConstant::Float(f) => Constant::Float(f),
157 CustomConstant::Str(s) => Constant::Str(s),
158 CustomConstant::Bool(b) => Constant::Bool(b),
159 CustomConstant::PyObj(obj) => Constant::Str(obj.to_string()),
160 CustomConstant::Tuple(t) => {
161 let tuple_items = t
162 .iter()
163 .map(|c| Constant::from(c.clone()))
164 .collect::<Vec<Constant>>();
165 Constant::Tuple(tuple_items)
166 }
167 CustomConstant::Dict(keys, values) => {
168 let tuple_items = keys
169 .iter()
170 .zip(values.iter())
171 .map(|(k, v)| {
172 Constant::Tuple(vec![Constant::Str(k.clone()), Constant::from(v.clone())])
173 })
174 .collect::<Vec<Constant>>();
175 Constant::Tuple(tuple_items)
176 }
177 }
178 }
179}
180
181impl From<Constant> for CustomConstant {
182 fn from(constant: Constant) -> Self {
183 match constant {
184 Constant::Int(i) => CustomConstant::Int(i),
185 Constant::Float(f) => CustomConstant::Float(f),
186 Constant::Str(s) => CustomConstant::Str(s),
187 Constant::Bool(b) => CustomConstant::Bool(b),
188 Constant::None => CustomConstant::Str("None".to_string()),
189 Constant::Tuple(t) => {
190 CustomConstant::Tuple(t.iter().map(|c| c.clone().into()).collect())
191 }
192 _ => panic!("Unsupported constant type"),
193 }
194 }
195}
196
197impl IntoPy<PyObject> for CustomConstant {
198 fn into_py(self, py: Python<'_>) -> PyObject {
199 match self {
200 CustomConstant::Int(i) => convert_bigint_to_i64(&i).into_py(py),
201 CustomConstant::Float(f) => f.into_py(py),
202 CustomConstant::Str(s) => s.into_py(py),
203 CustomConstant::Bool(b) => b.into_py(py),
204 CustomConstant::Tuple(t) => {
205 let py_list = t
206 .iter()
207 .map(|x| x.clone().into_py(py))
208 .collect::<Vec<PyObject>>();
209 py_list.into_py(py)
210 }
211 CustomConstant::PyObj(obj) => obj,
212 CustomConstant::Dict(keys, values) => {
213 let dict = PyDict::new(py);
214 for (key, value) in keys.iter().zip(values.iter()) {
215 dict.set_item(key, value.clone().into_py(py))
216 .unwrap_or_default();
217 }
218 dict.into_py(py)
219 }
220 }
221 }
222}
223
224type ToolFunction = Box<dyn Fn(Vec<Constant>) -> Result<CustomConstant, InterpreterError>>;
225type CustomToolFunction =
226 Box<dyn Fn(Vec<Constant>, HashMap<String, String>) -> Result<CustomConstant, InterpreterError>>;
227
228fn setup_custom_tools(tools: Vec<Box<dyn AnyTool>>) -> HashMap<String, CustomToolFunction> {
229 let mut tools_map = HashMap::new();
230 for tool in tools {
231 let tool_info = tool.tool_info();
232 tools_map.insert(
233 tool.name().to_string(),
234 Box::new(
235 move |args: Vec<Constant>, kwargs: HashMap<String, String>| {
236 let tool_parameter_names = tool_info.get_parameter_names();
238
239 let mut new_args = HashMap::new();
240 for (i, arg) in args.iter().enumerate() {
241 new_args
242 .insert(tool_parameter_names[i].clone(), arg.clone().str().unwrap());
243 }
244 for (key, value) in kwargs {
245 new_args.insert(key, value);
246 }
247 match tool.forward_json(json!(new_args)) {
248 Ok(results) => Ok(CustomConstant::Str(results)),
249 Err(e) => Ok(CustomConstant::Str(format!("Error: {}", e))),
250 }
251 },
252 ) as CustomToolFunction,
253 );
254 }
255 tools_map
256}
257
258pub fn setup_static_tools(
259 static_tools: HashMap<&'static str, &'static str>,
260) -> HashMap<String, ToolFunction> {
261 let mut tools = HashMap::new();
262 let static_tools_clone = static_tools.clone();
263 let eval_py = move |func: &str, args: Vec<Constant>| {
264 Python::with_gil(|py| {
265 let locals = PyDict::new(py);
266
267 let math = PyModule::import(py, "math")?;
269 locals.set_item("math", math)?;
270
271 for (i, arg) in args.iter().enumerate() {
272 match arg {
273 Constant::Float(f) => locals.set_item(format!("arg{}", i), f)?,
274 Constant::Int(int) => {
275 locals.set_item(format!("arg{}", i), convert_bigint_to_i64(int))?;
276 }
277 Constant::Str(s) => locals.set_item(format!("arg{}", i), s)?,
278 Constant::Tuple(t) => {
279 let py_list: Vec<f64> = t
280 .iter()
281 .map(|x| match x {
282 Constant::Float(f) => *f,
283 Constant::Int(i) => convert_bigint_to_f64(i),
284 _ => 0.0,
285 })
286 .collect();
287 locals.set_item(format!("arg{}", i), py_list)?
288 }
289 _ => locals.set_item(format!("arg{}", i), 0.0)?,
290 }
291 }
292
293 let arg_names: Vec<String> = (0..args.len()).map(|i| format!("arg{}", i)).collect();
294 let func_path = static_tools.get(func).unwrap_or(&"builtins.float");
295 let expr = format!("{}({})", func_path, arg_names.join(","));
296
297 let result = py.eval(&expr, None, Some(locals))?;
298 if let Ok(float_val) = result.extract::<f64>() {
300 Ok(CustomConstant::Float(float_val))
301 } else if let Ok(list_val) = result.extract::<Vec<String>>() {
302 Ok(CustomConstant::Tuple(
303 list_val.into_iter().map(CustomConstant::Str).collect(),
304 ))
305 } else if let Ok(string_val) = result.extract::<String>() {
306 Ok(CustomConstant::Str(string_val))
307 } else if let Ok(bool_val) = result.extract::<bool>() {
308 Ok(CustomConstant::Bool(bool_val))
309 } else if let Ok(int_val) = result.extract::<i64>() {
310 Ok(CustomConstant::Int(BigInt::from(int_val)))
311 } else {
312 Ok(CustomConstant::PyObj(result.into_py(py)))
313 }
314 })
315 };
316
317 for func in static_tools_clone.keys() {
319 let func = func.to_string(); let eval_py = eval_py.clone(); tools.insert(
322 func.clone(),
323 Box::new(move |args| eval_py(&func, args)) as ToolFunction,
324 );
325 }
326
327 tools
328}
329
330fn evaluate_stmt(
331 node: &ast::Stmt,
332 state: &mut HashMap<String, Box<dyn Any>>,
333 static_tools: &HashMap<String, StaticTool>,
334 custom_tools: &HashMap<String, CustomToolFunction>,
335) -> Result<CustomConstant, InterpreterError> {
336 match node {
337 Stmt::FunctionDef(func) => Ok(CustomConstant::Str(format!("Function: {:?}", func.name))),
338 Stmt::Expr(expr) => {
339 let result = evaluate_expr(&expr.value, state, static_tools, custom_tools)?;
340 Ok(result)
341 }
342 Stmt::For(for_stmt) => {
343 let iter = evaluate_expr(&for_stmt.iter.clone(), state, static_tools, custom_tools)?;
344 let values = match iter {
346 CustomConstant::PyObj(obj) => {
347 Python::with_gil(|py| -> Result<Vec<CustomConstant>, InterpreterError> {
348 let iter = obj.as_ref(py).iter()?;
349 let mut values = Vec::new();
350
351 for item in iter {
352 let item = item?;
353 if let Ok(num) = item.extract::<i64>() {
354 values.push(CustomConstant::Int(BigInt::from(num)));
355 } else if let Ok(float) = item.extract::<f64>() {
356 values.push(CustomConstant::Float(float));
357 } else if let Ok(string) = item.extract::<String>() {
358 values.push(CustomConstant::Str(string));
359 } else {
360 return Err(InterpreterError::RuntimeError(
361 "Unsupported type in iterator".to_string(),
362 ));
363 }
364 }
365 Ok(values)
366 })?
367 }
368 CustomConstant::Tuple(items) => items,
369 _ => {
370 return Err(InterpreterError::RuntimeError(
371 "Expected iterable".to_string(),
372 ))
373 }
374 };
375 let target_name = match &*for_stmt.target {
377 ast::Expr::Name(name) => name.id.to_string(),
378 _ => {
379 return Err(InterpreterError::RuntimeError(
380 "Expected name as loop target".to_string(),
381 ))
382 }
383 };
384 let mut for_loop_result = CustomConstant::Str(String::new());
385 for value in values {
387 state.insert(target_name.clone(), Box::new(value));
389
390 for stmt in &for_stmt.body {
392 for_loop_result = evaluate_stmt(stmt, state, static_tools, custom_tools)?;
393 }
394 }
395 Ok(for_loop_result)
396 }
397
398 Stmt::Assign(assign) => {
399 for target in assign.targets.iter() {
400 match target {
402 ast::Expr::Name(name) => {
403 let value =
404 evaluate_expr(&assign.value, state, static_tools, custom_tools)?;
405 state.insert(name.id.to_string(), Box::new(value));
406 }
407 ast::Expr::Tuple(target_names) => {
408 let value =
409 evaluate_expr(&assign.value, state, static_tools, custom_tools)?;
410 let values = value.tuple().ok_or_else(|| {
411 InterpreterError::RuntimeError(
412 "Tuple unpacking failed. Expected values of type tuple".to_string(),
413 )
414 })?;
415 if target_names.elts.len() != values.len() {
416 return Err(InterpreterError::RuntimeError(format!(
417 "Tuple unpacking failed. Expected {} values, got {}",
418 target_names.elts.len(),
419 values.len()
420 )));
421 }
422 for (i, target_name) in target_names.elts.iter().enumerate() {
423 match target_name {
424 ast::Expr::Name(name) => {
425 state.insert(name.id.to_string(), Box::new(values[i].clone()));
426 }
427 _ => panic!("Expected string"),
428 }
429 }
430 }
431 _ => panic!("Expected string"),
432 }
433 }
434 Ok(CustomConstant::Str(String::new()))
435 }
436
437 _ => Err(InterpreterError::RuntimeError(
438 "Unsupported statement".to_string(),
439 )),
440 }
441}
442
443fn evaluate_ast(
444 ast: &ast::Suite,
445 state: &mut HashMap<String, Box<dyn Any>>,
446 static_tools: &HashMap<String, StaticTool>,
447 custom_tools: &HashMap<String, CustomToolFunction>,
448) -> Result<CustomConstant, InterpreterError> {
449 let mut result = CustomConstant::Str(String::new());
450 for node in ast.iter() {
451 result = evaluate_stmt(node, state, static_tools, custom_tools)?;
452 }
453 Ok(result)
454}
455
456fn convert_bigint_to_f64(i: &BigInt) -> f64 {
457 let i = i.to_u32_digits();
458 let num = i.1.iter().fold(0i64, |acc, &d| acc * (1 << 32) + d as i64);
459 match i.0 {
460 Sign::Minus => -num as f64,
461 Sign::NoSign | Sign::Plus => num as f64,
462 }
463}
464fn convert_bigint_to_i64(i: &BigInt) -> i64 {
465 let i = i.to_u32_digits();
466 let num = i.1.iter().fold(0i64, |acc, &d| acc * (1 << 32) + d as i64);
467 match i.0 {
468 Sign::Minus => -num,
469 Sign::NoSign | Sign::Plus => num,
470 }
471}
472
473type StaticTool = Box<dyn Fn(Vec<Constant>) -> Result<CustomConstant, InterpreterError>>;
474type CustomTool =
475 Box<dyn Fn(Vec<Constant>, HashMap<String, String>) -> Result<CustomConstant, InterpreterError>>;
476
477fn evaluate_expr(
478 expr: &Expr,
479 state: &mut HashMap<String, Box<dyn Any>>,
480 static_tools: &HashMap<String, StaticTool>,
481 custom_tools: &HashMap<String, CustomTool>,
482) -> Result<CustomConstant, InterpreterError> {
483 match &expr {
484 ast::Expr::Dict(dict) => {
485 let keys = dict
486 .keys
487 .iter()
488 .map(|e| {
489 evaluate_expr(
490 &Box::new(e.clone().ok_or_else(|| {
491 InterpreterError::RuntimeError(
492 "Dictionary key cannot be None".to_string(),
493 )
494 })?),
495 state,
496 static_tools,
497 custom_tools,
498 )
499 .map(|c| c.str())
500 })
501 .collect::<Result<Vec<String>, _>>()?;
502 let values = dict
503 .values
504 .iter()
505 .map(|e| evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools))
506 .collect::<Result<Vec<CustomConstant>, _>>()?;
507 Ok(CustomConstant::Dict(keys, values))
508 }
509 ast::Expr::ListComp(list_comp) => {
510 let iter = evaluate_expr(
511 &list_comp.generators[0].iter,
512 state,
513 static_tools,
514 custom_tools,
515 )?;
516 let result = Python::with_gil(|py| -> Result<Vec<CustomConstant>, InterpreterError> {
517 let iter = iter.into_py(py);
518 let iter = iter.as_ref(py).iter()?;
519 let mut result = Vec::new();
520 for item in iter {
521 let target = match &list_comp.generators[0].target {
522 ast::Expr::Name(name) => name.id.to_string(),
523 _ => panic!("Expected string"),
524 };
525 let item = item?;
526 let item = extract_constant_from_pyobject(item, py)?;
527 state.insert(target, Box::new(item));
528 let eval_expr =
529 evaluate_expr(&list_comp.elt, state, static_tools, custom_tools)?;
530 result.push(eval_expr);
531 }
532 Ok(result)
533 });
534 let result = result?;
535 Ok(CustomConstant::Tuple(result))
536 }
537 ast::Expr::Call(call) => {
538 let args = call
539 .args
540 .iter()
541 .map(|e| evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools))
542 .collect::<Result<Vec<CustomConstant>, InterpreterError>>()?;
543 let func = match &*call.func {
544 ast::Expr::Name(name) => name.id.to_string(),
545 ast::Expr::Attribute(attr) => {
546 let obj = evaluate_expr(
547 &Box::new(*attr.value.clone()),
548 state,
549 static_tools,
550 custom_tools,
551 )?;
552
553 let func_name = attr.attr.to_string();
554 let output =
555 Python::with_gil(|py| -> Result<CustomConstant, InterpreterError> {
556 let obj = obj.into_py(py);
557 let func = obj.getattr(py, func_name.as_str())?;
558 let py_args = args
559 .iter()
560 .map(|a| match a {
561 CustomConstant::Float(f) => f.into_py(py),
563 CustomConstant::Int(i) => convert_bigint_to_i64(i).into_py(py),
564 _ => a.clone().into_py(py),
565 })
566 .collect::<Vec<PyObject>>();
567 let py_tuple = PyTuple::new(py, py_args);
568 let result = func.call1(py, py_tuple)?;
569
570 if func_name == "append"
572 || func_name == "extend"
573 || func_name == "insert"
574 {
575 let target = match &*attr.value {
576 ast::Expr::Name(name) => name.id.to_string(),
577 _ => panic!("Expected name"),
578 };
579 let out = extract_constant_from_pyobject(obj.as_ref(py), py)?;
580 state.insert(target, Box::new(out.clone()));
581 return Ok(out);
582 }
583
584 extract_constant_from_pyobject(result.as_ref(py), py)
585 });
586 return output;
587 }
588 _ => panic!("Expected function name"),
589 };
590
591 let keywords = call
592 .keywords
593 .iter()
594 .map(|k| {
595 let value = evaluate_expr(
596 &Box::new(k.value.clone()),
597 state,
598 static_tools,
599 custom_tools,
600 )?;
601 Ok((k.arg.as_ref().unwrap().to_string(), value.str()))
602 })
603 .collect::<Result<HashMap<String, String>, InterpreterError>>()?;
604 if func == "final_answer" {
605 if let Some(answer) = keywords.get("answer") {
606 return Err(InterpreterError::FinalAnswer(answer.to_string()));
607 } else {
608 return Err(InterpreterError::FinalAnswer(
609 args.iter()
610 .map(|c| c.str())
611 .collect::<Vec<String>>()
612 .join(" "),
613 ));
614 }
615 }
616 if func == "print" {
617 match state.get_mut("print_logs") {
618 Some(logs) => {
619 if let Some(logs) = logs.downcast_mut::<Vec<String>>() {
620 logs.push(
621 args.iter()
622 .map(|c| c.str())
623 .collect::<Vec<String>>()
624 .join(" "),
625 );
626 } else {
627 return Err(InterpreterError::RuntimeError(
628 "print_logs is not a list".to_string(),
629 ));
630 }
631 }
632 None => {
633 state.insert(
634 "print_logs".to_string(),
635 Box::new(args.iter().map(|c| c.str()).collect::<Vec<String>>()),
636 );
637 }
638 }
639 return Ok(CustomConstant::Str(
640 args.iter()
641 .map(|c| c.str())
642 .collect::<Vec<String>>()
643 .join(" "),
644 ));
645 }
646 if static_tools.contains_key(&func) {
647 let result =
648 static_tools[&func](args.iter().map(|c| Constant::from(c.clone())).collect());
649 result
650 } else if custom_tools.contains_key(&func) {
651 let result = custom_tools[&func](
652 args.iter().map(|c| Constant::from(c.clone())).collect(),
653 keywords,
654 );
655 result
656 } else {
657 Err(InterpreterError::RuntimeError(format!(
658 "Function '{}' not found",
659 func
660 )))
661 }
662 }
663 ast::Expr::BinOp(binop) => {
664 let left_val_exp =
665 evaluate_expr(&binop.left.clone(), state, static_tools, custom_tools)?;
666 let right_val_exp: CustomConstant =
667 evaluate_expr(&binop.right.clone(), state, static_tools, custom_tools)?;
668
669 match binop.op {
670 Operator::Add => match (left_val_exp.clone(), right_val_exp.clone()) {
671 (CustomConstant::Str(s), CustomConstant::Str(s2)) => {
672 return Ok(CustomConstant::Str(s + &s2));
673 }
674 (CustomConstant::Str(s), CustomConstant::Int(i)) => {
675 return Ok(CustomConstant::Str(s + &i.to_string()));
676 }
677 (CustomConstant::Int(i), CustomConstant::Str(s)) => {
678 return Ok(CustomConstant::Str(i.to_string() + &s));
679 }
680 _ => {}
681 },
682 Operator::Mult => match (left_val_exp.clone(), right_val_exp.clone()) {
683 (CustomConstant::Str(s), CustomConstant::Int(i)) => {
684 return Ok(CustomConstant::Str(
685 s.repeat(convert_bigint_to_i64(&i) as usize),
686 ));
687 }
688 (CustomConstant::Int(i), CustomConstant::Str(s)) => {
689 return Ok(CustomConstant::Str(
690 s.repeat(convert_bigint_to_i64(&i) as usize),
691 ));
692 }
693 _ => {}
694 },
695 _ => {}
696 }
697 let left_val = match left_val_exp.clone() {
698 CustomConstant::Float(f) => f,
699 CustomConstant::Int(i) => convert_bigint_to_f64(&i),
700 _ => panic!("Expected float or int"),
701 };
702 let right_val = match right_val_exp.clone() {
703 CustomConstant::Float(f) => f,
704 CustomConstant::Int(i) => convert_bigint_to_f64(&i),
705 _ => panic!("Expected float or int"),
706 };
707
708 match &binop.op {
709 Operator::Add => Ok(CustomConstant::Float(left_val + right_val)),
710 Operator::Sub => Ok(CustomConstant::Float(left_val - right_val)),
711 Operator::Mult => Ok(CustomConstant::Float(left_val * right_val)),
712 Operator::Div => Ok(CustomConstant::Float(left_val / right_val)),
713 Operator::FloorDiv => Ok(CustomConstant::Float(left_val / right_val)),
714 Operator::Mod => Ok(CustomConstant::Float(left_val % right_val)),
715 Operator::Pow => Ok(CustomConstant::Float(left_val.powf(right_val))),
716 Operator::BitOr => Ok(CustomConstant::Int(BigInt::from(
717 left_val as i64 | right_val as i64,
718 ))),
719 Operator::BitXor => Ok(CustomConstant::Int(BigInt::from(
720 left_val as i64 ^ right_val as i64,
721 ))),
722 Operator::BitAnd => Ok(CustomConstant::Int(BigInt::from(
723 left_val as i64 & right_val as i64,
724 ))),
725 Operator::LShift => {
726 let left_val = left_val as i64;
727 let right_val = right_val as i64;
728 Ok(CustomConstant::Int(BigInt::from(left_val << right_val)))
729 }
730 Operator::RShift => {
731 let left_val = left_val as i64;
732 let right_val = right_val as i64;
733 Ok(CustomConstant::Int(BigInt::from(left_val >> right_val)))
734 }
735 Operator::MatMult => Ok(CustomConstant::Float(left_val * right_val)),
736 }
737 }
738 ast::Expr::UnaryOp(unaryop) => {
739 let operand = evaluate_expr(&unaryop.operand, state, static_tools, custom_tools)?;
740 match &unaryop.op {
741 UnaryOp::USub => match operand {
742 CustomConstant::Float(f) => Ok(CustomConstant::Float(-f)),
743 CustomConstant::Int(i) => Ok(CustomConstant::Int(-i)),
744 _ => panic!("Expected float or int"),
745 },
746 UnaryOp::UAdd => Ok(operand),
747 UnaryOp::Not => {
748 if let CustomConstant::Bool(b) = operand {
749 Ok(CustomConstant::Bool(!b))
750 } else {
751 panic!("Expected boolean")
752 }
753 }
754 UnaryOp::Invert => {
755 if let CustomConstant::Float(f) = operand {
756 Ok(CustomConstant::Float(-(f as i64) as f64))
757 } else {
758 panic!("Expected float")
759 }
760 }
761 }
762 }
763 ast::Expr::Constant(constant) => match &constant.value {
764 Constant::Int(i) => Ok(CustomConstant::Int(i.clone())),
765 _ => Ok(constant.value.clone().into()),
766 },
767 ast::Expr::List(list) => Ok(CustomConstant::Tuple(
768 list.elts
769 .iter()
770 .map(|e| evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools))
771 .collect::<Result<Vec<CustomConstant>, _>>()?,
772 )),
773 ast::Expr::Name(name) => {
774 if let Some(value) = state.get(name.id.as_str()) {
775 if let Some(constant) = value.downcast_ref::<CustomConstant>() {
776 Ok(constant.clone())
777 } else {
778 Err(InterpreterError::RuntimeError(format!(
779 "Error in downcasting constant {}",
780 name.id
781 )))
782 }
783 } else {
784 Err(InterpreterError::RuntimeError(format!(
785 "Variable '{}' used before assignment",
786 name.id
787 )))
788 }
789 }
790 ast::Expr::Tuple(tuple) => Ok(CustomConstant::Tuple(
791 tuple
792 .elts
793 .iter()
794 .map(|e| evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools))
795 .collect::<Result<Vec<CustomConstant>, _>>()?,
796 )),
797 ast::Expr::JoinedStr(joinedstr) => Ok(CustomConstant::Str(
798 joinedstr
799 .values
800 .iter()
801 .map(|e| {
802 evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools)
803 .map(|result| result.str())
804 })
805 .collect::<Result<Vec<String>, _>>()?
806 .join(""),
807 )),
808 ast::Expr::FormattedValue(formattedvalue) => {
809 let result = evaluate_expr(&formattedvalue.value, state, static_tools, custom_tools)?;
810
811 Ok(CustomConstant::Str(result.str()))
812 }
813 ast::Expr::Subscript(subscript) => {
814 let result = Python::with_gil(|py| {
815 let value = evaluate_expr(&subscript.value, state, static_tools, custom_tools)?;
817 let value_obj = value.into_py(py);
818
819 let slice = Constant::from(evaluate_expr(
820 &subscript.slice,
821 state,
822 static_tools,
823 custom_tools,
824 )?);
825
826 if let Constant::Int(i) = slice {
828 let index = convert_bigint_to_i64(&i);
829 let result = value_obj.as_ref(py).get_item(index);
830 match result {
831 Ok(result) => return extract_constant_from_pyobject(result, py),
832 Err(e) => return Err(InterpreterError::RuntimeError(e.to_string())),
833 }
834 }
835
836 if let Constant::Str(s) = slice {
838 if let Ok(dict) = value_obj.as_ref(py).downcast::<PyDict>() {
840 let result = dict.get_item(s.clone());
841 match result {
842 Some(value) => return extract_constant_from_pyobject(value, py),
843 None => {
844 return Err(InterpreterError::RuntimeError(format!(
845 "KeyError: '{}'",
846 s
847 )))
848 }
849 }
850 }
851 }
852
853 let result = match &*subscript.slice {
855 ast::Expr::Slice(slice) => {
857 let start = match &slice.lower {
858 Some(lower) => {
859 evaluate_expr(lower, state, static_tools, custom_tools)?.into()
860 }
861 None => None,
862 };
863 let start = start
864 .map(|start| {
865 let constant = Constant::from(start);
866 constant
867 .int()
868 .map(|i| convert_bigint_to_i64(&i))
869 .ok_or_else(|| {
870 InterpreterError::RuntimeError(
871 "Invalid start value in slice".to_string(),
872 )
873 })
874 })
875 .transpose()?;
876
877 let stop = match &slice.upper {
878 Some(upper) => {
879 evaluate_expr(upper, state, static_tools, custom_tools)?.into()
880 }
881 None => None,
882 };
883 let stop = stop
884 .map(|stop| {
885 let constant = Constant::from(stop);
886 constant
887 .int()
888 .map(|i| convert_bigint_to_i64(&i))
889 .ok_or_else(|| {
890 InterpreterError::RuntimeError(
891 "Invalid stop value in slice".to_string(),
892 )
893 })
894 })
895 .transpose()?;
896
897 let step = match &slice.step {
898 Some(step) => {
899 evaluate_expr(step, state, static_tools, custom_tools)?.into()
900 }
901 None => None,
902 };
903 let step = step
904 .map(|step| {
905 let constant = Constant::from(step);
906 constant
907 .int()
908 .map(|i| convert_bigint_to_i64(&i))
909 .ok_or_else(|| {
910 InterpreterError::RuntimeError(
911 "Invalid step value in slice".to_string(),
912 )
913 })
914 })
915 .transpose()?;
916
917 let slice_obj = py
918 .eval("slice", None, None)?
919 .call1((start, stop, step))?
920 .into_py(py);
921 value_obj.as_ref(py).get_item(slice_obj)?
922 }
923 _ => return Err(InterpreterError::RuntimeError("Invalid slice".to_string())),
924 };
925
926 extract_constant_from_pyobject(result, py)
928 });
929 result
930 }
931 ast::Expr::Slice(slice) => {
932 let start = match &slice.lower {
933 Some(lower) => evaluate_expr(lower, state, static_tools, custom_tools)?,
934 None => CustomConstant::Int(BigInt::from(0)),
935 };
936 let end = match &slice.upper {
937 Some(upper) => evaluate_expr(upper, state, static_tools, custom_tools)?,
938 None => CustomConstant::Int(BigInt::from(0)),
939 };
940 let step = match &slice.step {
941 Some(step) => evaluate_expr(step, state, static_tools, custom_tools)?,
942 None => CustomConstant::Int(BigInt::from(1)),
943 };
944 Ok(CustomConstant::Tuple(vec![start, end, step]))
945 }
946 _ => {
947 panic!("Unsupported expression: {:?}", expr);
948 }
949 }
950}
951
952fn extract_constant_from_pyobject(
953 obj: &PyAny,
954 py: Python<'_>,
955) -> Result<CustomConstant, InterpreterError> {
956 if let Ok(float_val) = obj.extract::<f64>() {
957 Ok(CustomConstant::Float(float_val))
958 } else if let Ok(string_val) = obj.extract::<String>() {
959 Ok(CustomConstant::Str(string_val))
960 } else if let Ok(bool_val) = obj.extract::<bool>() {
961 Ok(CustomConstant::Bool(bool_val))
962 } else if let Ok(int_val) = obj.extract::<i64>() {
963 Ok(CustomConstant::Int(BigInt::from(int_val)))
964 } else if let Ok(list_val) = obj.extract::<Vec<String>>() {
965 Ok(CustomConstant::Tuple(
966 list_val.into_iter().map(CustomConstant::Str).collect(),
967 ))
968 } else if let Ok(list_val) = obj.extract::<Vec<i64>>() {
969 Ok(CustomConstant::Tuple(
970 list_val
971 .into_iter()
972 .map(|i| CustomConstant::Int(BigInt::from(i)))
973 .collect(),
974 ))
975 } else if let Ok(list_val) = obj.extract::<Vec<f64>>() {
976 Ok(CustomConstant::Tuple(
977 list_val.into_iter().map(CustomConstant::Float).collect(),
978 ))
979 } else if let Ok(dict_value) = obj.extract::<&PyDict>() {
980 let keys = dict_value
981 .keys()
982 .iter()
983 .map(|key| key.extract::<String>())
984 .collect::<Result<Vec<String>, _>>()?;
985 let values = dict_value
986 .values()
987 .iter()
988 .map(|value| extract_constant_from_pyobject(value, py))
989 .collect::<Result<Vec<CustomConstant>, _>>()?;
990 Ok(CustomConstant::Dict(keys, values))
991 } else {
992 Ok(CustomConstant::PyObj(obj.into_py(py)))
993 }
994}
995pub fn evaluate_python_code(
996 code: &str,
997 custom_tools: Vec<Box<dyn AnyTool>>,
998 state: &mut HashMap<String, Box<dyn Any>>,
999) -> Result<String, InterpreterError> {
1000 let base_tools = get_base_python_tools();
1001 let static_tools = setup_static_tools(base_tools);
1002 let custom_tools = setup_custom_tools(custom_tools);
1003 let ast = ast::Suite::parse(code, "<embedded>")
1004 .map_err(|e| InterpreterError::SyntaxError(e.to_string()))?;
1005
1006 let result = evaluate_ast(&ast, state, &static_tools, &custom_tools)?;
1007 Ok(result.str())
1008}
1009
1010pub struct LocalPythonInterpreter {
1011 static_tools: HashMap<String, ToolFunction>,
1012 custom_tools: HashMap<String, CustomToolFunction>,
1013}
1014
1015impl LocalPythonInterpreter {
1016 pub fn new(custom_tools: Vec<Box<dyn AnyTool>>) -> Self {
1017 let custom_tools = setup_custom_tools(custom_tools);
1018 let base_tools = get_base_python_tools();
1019 let static_tools = setup_static_tools(base_tools);
1020 Self {
1021 static_tools,
1022 custom_tools,
1023 }
1024 }
1025 pub fn forward(
1026 &self,
1027 code: &str,
1028 state: &mut Option<HashMap<String, Box<dyn Any>>>,
1029 ) -> Result<(String, String), InterpreterError> {
1030 let mut empty_state = HashMap::new();
1031 let ast = ast::Suite::parse(code, "<embedded>")
1032 .map_err(|e| InterpreterError::SyntaxError(e.to_string()))?;
1033 let state = state.as_mut().unwrap_or(&mut empty_state);
1034 let result = evaluate_ast(&ast, state, &self.static_tools, &self.custom_tools)?;
1035
1036 let mut empty_string = Vec::new();
1037 let execution_logs = state
1038 .get_mut("print_logs")
1039 .and_then(|logs| logs.downcast_mut::<Vec<String>>())
1040 .unwrap_or(&mut empty_string)
1041 .join("\n");
1042 Ok((result.str(), execution_logs))
1043 }
1044}
1045#[cfg(test)]
1046mod tests {
1047 use super::*;
1048 use crate::tools::{DuckDuckGoSearchTool, FinalAnswerTool, VisitWebsiteTool};
1049 use std::collections::HashMap;
1050
1051 #[test]
1052 fn test_evaluate_python_code() {
1053 let code = "print('Hello, world!')";
1054 let mut state = HashMap::new();
1055 let result = evaluate_python_code(code, vec![], &mut state).unwrap();
1056 assert_eq!(result, "Hello, world!");
1057 }
1058
1059 #[test]
1060 fn test_evaluate_python_code_with_joined_str() {
1061 let code = r#"word = 'strawberry'
1062r_count = word.count('r')
1063print(f"The letter 'r' appears {r_count} times in the word '{word}'.")"#;
1064 let mut state = HashMap::new();
1065 let result = evaluate_python_code(code, vec![], &mut state).unwrap();
1066 assert_eq!(
1067 result,
1068 "The letter 'r' appears 3 times in the word 'strawberry'."
1069 );
1070 }
1071
1072 #[test]
1073 fn test_final_answer_execution() {
1074 let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(FinalAnswerTool::new())];
1075 let mut state = HashMap::new();
1076 let result =
1077 evaluate_python_code("final_answer(answer='Hello, world!')", tools, &mut state);
1078 assert_eq!(
1079 result,
1080 Err(InterpreterError::FinalAnswer("Hello, world!".to_string()))
1081 );
1082 }
1083
1084 #[test]
1085 fn test_evaluate_python_code_with_subscript() {
1086 let code = textwrap::dedent(
1087 r#"
1088 word = 'strawberry'
1089 print(word[3])"#,
1090 );
1091 let mut state = HashMap::new();
1092 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1093 assert_eq!(result, "a");
1094
1095 let code = textwrap::dedent(
1096 r#"
1097 word = 'strawberry'
1098 print(word[-3])"#,
1099 );
1100 let mut state = HashMap::new();
1101 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1102 assert_eq!(result, "r");
1103
1104 let code = textwrap::dedent(
1105 r#"
1106 word = 'strawberry'
1107 print(word[9])"#,
1108 );
1109 let mut state = HashMap::new();
1110 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1111 assert_eq!(result, "y");
1112
1113 let code = textwrap::dedent(
1114 r#"
1115 word = 'strawberry'
1116 print(word[10])"#,
1117 );
1118 let mut state = HashMap::new();
1119 let result = evaluate_python_code(&code, vec![], &mut state);
1120 assert_eq!(
1121 result,
1122 Err(InterpreterError::RuntimeError(
1123 "IndexError: string index out of range".to_string()
1124 ))
1125 );
1126
1127 let code = textwrap::dedent(
1128 r#"
1129 numbers = [1, 2, 3, 4, 5]
1130 print(numbers[1])"#,
1131 );
1132 let mut state = HashMap::new();
1133 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1134 assert_eq!(result, "2");
1135
1136 let code = textwrap::dedent(
1137 r#"
1138 numbers = [1, 2, 3, 4, 5]
1139 print(numbers[-5])"#,
1140 );
1141 let mut state = HashMap::new();
1142 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1143 assert_eq!(result, "1");
1144
1145 let code = textwrap::dedent(
1146 r#"
1147 numbers = [1, 2, 3, 4, 5]
1148 print(numbers[-6])"#,
1149 );
1150 let mut state = HashMap::new();
1151 let result = evaluate_python_code(&code, vec![], &mut state);
1152 assert_eq!(
1153 result,
1154 Err(InterpreterError::RuntimeError(
1155 "IndexError: list index out of range".to_string()
1156 ))
1157 );
1158 }
1159
1160 #[test]
1161 fn test_evaluate_python_code_with_slice() {
1162 let code = textwrap::dedent(
1163 r#"
1164 numbers = [1, 2, 3, 4, 5]
1165 print(numbers[1:3])"#,
1166 );
1167 let mut state = HashMap::new();
1168 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1169 assert_eq!(result, "[2, 3]");
1170
1171 let code = textwrap::dedent(
1172 r#"
1173 numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1174 print(numbers[1:5:2])"#,
1175 );
1176 let mut state = HashMap::new();
1177 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1178 assert_eq!(result, "[2, 4]");
1179
1180 let code = textwrap::dedent(
1181 r#"
1182 numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1183 print(numbers[5:1:-2])"#,
1184 );
1185 let mut state = HashMap::new();
1186 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1187 assert_eq!(result, "[6, 4]");
1188
1189 let code = textwrap::dedent(
1190 r#"
1191 word = 'strawberry'
1192 print(word[::-1])"#,
1193 );
1194 let mut state = HashMap::new();
1195 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1196 assert_eq!(result, "yrrebwarts");
1197
1198 let code = textwrap::dedent(
1199 r#"
1200 numbers = [1, 2, 3, 4, 5]
1201 print(numbers[::-1])"#,
1202 );
1203 let mut state = HashMap::new();
1204 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1205 assert_eq!(result, "[5, 4, 3, 2, 1]");
1206 }
1207
1208 #[test]
1209 fn test_for_loop() {
1210 let code = textwrap::dedent(
1211 r#"
1212 for i in range(5):
1213 print(i)
1214 "#,
1215 );
1216 let mut state = HashMap::new();
1217 let _ = evaluate_python_code(&code, vec![], &mut state).unwrap();
1218 assert_eq!(
1219 state
1220 .get("print_logs")
1221 .unwrap()
1222 .downcast_ref::<Vec<String>>()
1223 .unwrap(),
1224 &vec!["0", "1", "2", "3", "4"]
1225 );
1226 }
1227
1228 #[test]
1229 fn test_for_loop_with_tools() {
1230 let code = textwrap::dedent(
1231 r#"
1232 for i in range(5):
1233 search = duckduckgo_search(query=i)
1234 print(search)
1235 "#,
1236 );
1237 let mut state = HashMap::new();
1238 let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(DuckDuckGoSearchTool::new())];
1239 let _ = evaluate_python_code(&code, tools, &mut state).unwrap();
1240 }
1241
1242 #[test]
1243 fn test_evaluate_python_code_with_dict() {
1244 let code = textwrap::dedent(
1245 r#"
1246 my_dict = {'a': "1", 'b': "2", 'c': "3"}
1247 print(f"my_dict['a'] is {my_dict['a']}")
1248 "#,
1249 );
1250 let mut state = HashMap::new();
1251 let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1252 assert_eq!(result, "my_dict['a'] is 1");
1253
1254 let code = textwrap::dedent(
1255 r#"
1256dinner_places = [
1257 {
1258 "title": "25 Best Restaurants in Berlin, By Local Foodies",
1259 "url": "https://www.timeout.com/berlin/restaurants/best-restaurants-in-berlin"
1260 },
1261 {
1262 "title": "The 38 Best Berlin Restaurants - Eater",
1263 "url": "https://www.eater.com/maps/best-restaurants-berlin"
1264 },
1265 {
1266 "title": "THE 10 BEST Restaurants in Berlin - Tripadvisor",
1267 "url": "https://www.tripadvisor.com/Restaurants-g187323-Berlin.html"
1268 },
1269 {
1270 "title": "12 Unique Restaurants in Berlin",
1271 "url": "https://www.myglobalviewpoint.com/unique-restaurants-in-berlin/"
1272 },
1273 {
1274 "title": "Berlin's best restaurants: 101 places to eat right now",
1275 "url": "https://www.the-berliner.com/food/best-restaurants-berlin-101-places-to-eat/"
1276 }
1277]
1278
1279for place in dinner_places:
1280 print(f"{place['title']}: {place['url']}")
1281 "#,
1282 );
1283 let state = HashMap::new();
1284 let local_python_interpreter = LocalPythonInterpreter::new(vec![]);
1285 let (_, execution_logs) = local_python_interpreter
1286 .forward(&code, &mut Some(state))
1287 .unwrap();
1288 assert_eq!(execution_logs, "25 Best Restaurants in Berlin, By Local Foodies: https://www.timeout.com/berlin/restaurants/best-restaurants-in-berlin\nThe 38 Best Berlin Restaurants - Eater: https://www.eater.com/maps/best-restaurants-berlin\nTHE 10 BEST Restaurants in Berlin - Tripadvisor: https://www.tripadvisor.com/Restaurants-g187323-Berlin.html\n12 Unique Restaurants in Berlin: https://www.myglobalviewpoint.com/unique-restaurants-in-berlin/\nBerlin's best restaurants: 101 places to eat right now: https://www.the-berliner.com/food/best-restaurants-berlin-101-places-to-eat/");
1289
1290 let code = textwrap::dedent(
1291 r#"
1292movies = [
1293 {"title": "Babygirl", "showtimes": ["12:50 pm", "6:20 pm"]},
1294 {"title": "Better Man", "showtimes": ["9:20 pm"]},
1295 {"title": "La acompañante", "showtimes": ["3:40 pm", "6:30 pm", "9:10 pm"]},
1296 {"title": "Amenaza en el aire", "showtimes": ["9:30 pm"]},
1297 {"title": "Juf Braaksel en de Geniale Ontsnapping", "showtimes": ["12:30 pm"]},
1298 {"title": "Juffrouw Pots", "showtimes": ["10:35 am", "3:50 pm"]},
1299 {"title": "K3 en Het Lied van de Zeemeermin", "showtimes": ["10:00 am"]},
1300 {"title": "Marked Men", "showtimes": ["2:50 pm", "6:50 pm"]},
1301 {"title": "Vaiana 2", "showtimes": ["11:10 am", "12:40 pm"]},
1302 {"title": "Mufasa: El rey león", "showtimes": ["10:20 am", "3:10 pm", "9:00 pm"]},
1303 {"title": "Paddington: Aventura en la selva", "showtimes": ["12:20 pm", "3:30 pm", "6:10 pm"]},
1304 {"title": "Royal Opera House: The Tales of Hoffmann", "showtimes": ["1:30 pm"]},
1305 {"title": "The Growcodile", "showtimes": ["10:10 am"]},
1306 {"title": "Vivir el momento", "showtimes": ["5:20 pm"]},
1307 {"title": "Wicked", "showtimes": ["7:00 pm"]},
1308 {"title": "Woezel & Pip op Avontuur in de Tovertuin", "showtimes": ["10:30 am", "1:50 pm"]}
1309]
1310
1311for movie in movies:
1312 print(f"{movie['title']}: {', '.join(movie['showtimes'])}")
1313
1314 "#,
1315 );
1316 let state = HashMap::new();
1317 let local_python_interpreter = LocalPythonInterpreter::new(vec![]);
1318 let (_, _) = local_python_interpreter
1319 .forward(&code, &mut Some(state))
1320 .unwrap();
1321
1322 let code = textwrap::dedent(
1323 r#"
1324urls = [
1325 "https://www.tripadvisor.com/Restaurants-g187323-Berlin.html",
1326 "https://www.timeout.com/berlin/restaurants/best-restaurants-in-berlin"
1327]
1328
1329for url in urls:
1330 page_content = duckduckgo_search(url)
1331 print(page_content)
1332 print("\n" + "="*80 + "\n") # Print separator between pages
1333 "#,
1334 );
1335 let mut state = HashMap::new();
1336 let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(DuckDuckGoSearchTool::new())];
1337 let _ = evaluate_python_code(&code, tools, &mut state).unwrap();
1338 }
1339
1340 #[test]
1341 fn test_evaluate_python_code_with_list_comprehension() {
1342 let code = textwrap::dedent(
1343 r#"
1344 a = [1,2,3]
1345 print([x for x in a])
1346 "#,
1347 );
1348 let mut state = HashMap::new();
1349 let _ = evaluate_python_code(&code, vec![], &mut state).unwrap();
1350 assert_eq!(
1351 state
1352 .get("print_logs")
1353 .unwrap()
1354 .downcast_ref::<Vec<String>>()
1355 .unwrap(),
1356 &vec!["[1, 2, 3]"]
1357 );
1358 }
1359
1360 #[test]
1361 fn test_evaluate_python_code_append_to_list() {
1362 let code = textwrap::dedent(
1363 r#"
1364 a = [1,2,3]
1365 a.append(4)
1366 print(a)
1367 "#,
1368 );
1369 let mut state = HashMap::new();
1370 let _ = evaluate_python_code(&code, vec![], &mut state).unwrap();
1371 assert_eq!(
1372 state
1373 .get("print_logs")
1374 .unwrap()
1375 .downcast_ref::<Vec<String>>()
1376 .unwrap(),
1377 &vec!["[1, 2, 3, 4]"]
1378 );
1379
1380 let code = textwrap::dedent(
1381 r#"
1382urls = [
1383 "https://www.imdb.com/showtimes/cinema/ES/ci1028808/ES/08520",
1384 "https://en.pathe.nl/bioscoopagenda",
1385 "https://www.filmvandaag.nl/bioscoop?filter=64"
1386]
1387movies = []
1388for url in urls:
1389 page_content = url
1390 movies.append(page_content)
1391
1392print(movies)
1393 "#,
1394 );
1395 let mut state = HashMap::new();
1396 let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(VisitWebsiteTool::new())];
1397 let _ = evaluate_python_code(&code, tools, &mut state).unwrap();
1398 assert_eq!(
1399 state
1400 .get("print_logs")
1401 .unwrap()
1402 .downcast_ref::<Vec<String>>()
1403 .unwrap(),
1404 &vec!["[https://www.imdb.com/showtimes/cinema/ES/ci1028808/ES/08520, https://en.pathe.nl/bioscoopagenda, https://www.filmvandaag.nl/bioscoop?filter=64]"]
1405 );
1406 }
1407
1408 #[test]
1409 fn test_evaluate_python_code_with_error() {
1410 let code = textwrap::dedent(
1411 r#"
1412cycling_paths = [
1413 {
1414 "title": "5x Cycling routes in and around Eindhoven",
1415 "snippet": "One of the well-known cycling routes in and around Eindhoven is Rondje Eindhoven, which means 'around Eindhoven'. This route starts in four different directions. The distances are between 20 and 75 kilometres and take you through the city center, a number of dynamic districts, and beautiful nature.",
1416 "url": "https://www.thisiseindhoven.com/en/see-and-do/fun-things-to-do/cycling-routes-in-and-around-eindhoven"
1417 },
1418 {
1419 "title": "Top 5 Bike Rides and Cycling Routes around Eindhoven - Komoot",
1420 "snippet": "Explore the top cycling routes around Eindhoven to experience more of this area. We bring you the top bike rides around Eindhoven — all you've got to do is pick the one that's right for you.",
1421 "url": "https://www.komoot.com/guide/893152/cycling-around-eindhoven"
1422 },
1423 {
1424 "title": "Cycling routes in Eindhoven - Bikemap",
1425 "snippet": "Find cycle routes in Eindhoven: Round trips | Relaxed routes | Gravel routes | Road bike routes | MTB routes | Trekking routes.",
1426 "url": "https://www.bikemap.net/en/l/2756253/"
1427 }
1428]
1429
1430print("\n".join([f"{path['title']}: {path['snippet']} (More info: {path['url']})" for path in cycling_paths]))
1431
1432
1433 "#,
1434 );
1435 let state = HashMap::new();
1436 let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(VisitWebsiteTool::new())];
1437 let local_python_interpreter = LocalPythonInterpreter::new(tools);
1438 let (_, logs) = local_python_interpreter
1439 .forward(&code, &mut Some(state))
1440 .unwrap();
1441 println!("logs: {:?}", logs);
1442 }
1443}