use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; use rhai::{Array, Dynamic, Engine, EvalAltResult, Map, Scope}; use std::collections::HashMap; /// Convert Rhai Dynamic to Python object fn dynamic_to_python(py: Python<'_>, value: Dynamic) -> PyResult { match value.type_name() { "i64" => Ok(value.as_int().unwrap().to_object(py)), "f64" => Ok(value.as_float().unwrap().to_object(py)), "bool" => Ok(value.as_bool().unwrap().to_object(py)), "string" => Ok(value.into_string().unwrap().to_object(py)), "char" => Ok(value.as_char().unwrap().to_string().to_object(py)), "()" => Ok(py.None()), "array" => { let array = value.cast::(); let py_list = PyList::empty(py); for item in array { py_list.append(dynamic_to_python(py, item)?)?; } Ok(py_list.to_object(py)) } "map" => { let map = value.cast::(); let py_dict = PyDict::new(py); for (key, value) in map { let py_key = key.to_object(py); let py_value = dynamic_to_python(py, value)?; py_dict.set_item(py_key, py_value)?; } Ok(py_dict.to_object(py)) } _ => Err(PyRuntimeError::new_err(format!( "Unsupported Rhai type: {}", value.type_name() ))), } } /// Convert Python object to Rhai Dynamic fn python_to_dynamic(py: Python<'_>, obj: &PyAny) -> PyResult { if obj.is_none() { Ok(Dynamic::UNIT) } else if let Ok(val) = obj.extract::() { Ok(Dynamic::from(val)) } else if let Ok(val) = obj.extract::() { Ok(Dynamic::from(val)) } else if let Ok(val) = obj.extract::() { Ok(Dynamic::from(val)) } else if let Ok(val) = obj.extract::() { Ok(Dynamic::from(val)) } else if let Ok(py_list) = obj.downcast::() { let mut array = Array::new(); for item in py_list { array.push(python_to_dynamic(py, item)?); } Ok(Dynamic::from(array)) } else if let Ok(py_dict) = obj.downcast::() { let mut map = Map::new(); for (key, value) in py_dict { let key_str = key.extract::()?; let rhai_value = python_to_dynamic(py, value)?; map.insert(key_str.into(), rhai_value); } Ok(Dynamic::from(map)) } else { Err(PyRuntimeError::new_err(format!( "Unsupported Python type: {}", obj.get_type().name()? ))) } } #[pyclass] struct RhaiEngine { engine: Engine, scope: Scope<'static>, } #[pymethods] impl RhaiEngine { #[new] fn new() -> Self { Self { engine: Engine::new(), scope: Scope::new(), } } /// Evaluate a Rhai script and return the result fn eval(&mut self, py: Python<'_>, script: &str) -> PyResult { match self .engine .eval_with_scope::(&mut self.scope, script) { Ok(result) => dynamic_to_python(py, result), Err(err) => Err(PyRuntimeError::new_err(format!("Rhai error: {}", err))), } } /// Set a variable in the engine scope fn set_var(&mut self, py: Python<'_>, name: &str, value: &PyAny) -> PyResult<()> { let dynamic_value = python_to_dynamic(py, value)?; self.scope.set_value(name, dynamic_value); Ok(()) } /// Get a variable from the engine scope fn get_var(&self, py: Python<'_>, name: &str) -> PyResult { match self.scope.get_value::(name) { Some(value) => dynamic_to_python(py, value), None => Err(PyRuntimeError::new_err(format!( "Variable '{}' not found", name ))), } } /// Check if a variable exists in the scope fn has_var(&self, name: &str) -> bool { self.scope.contains(name) } /// Clear all variables from the scope fn clear_scope(&mut self) { self.scope.clear(); } /// Compile a script and return whether it's valid fn compile(&self, script: &str) -> PyResult { match self.engine.compile(script) { Ok(_) => Ok(true), Err(err) => Err(PyRuntimeError::new_err(format!( "Compilation error: {}", err ))), } } /// Get the list of all variable names in the scope fn list_vars(&self) -> Vec { self.scope .iter() .map(|(name, _)| name.to_string()) .collect() } } /// A Python module implemented in Rust using PyO3 #[pymodule] fn rhai_python(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add("__version__", "0.1.0")?; Ok(()) }