use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; use pyo3::IntoPyObjectExt; use rhai::{Array, Dynamic, Engine, Map, Scope}; /// 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().into_py_any(py).unwrap()), "f64" => Ok(value.as_float().unwrap().into_py_any(py).unwrap()), "bool" => Ok(value.as_bool().unwrap().into_py_any(py).unwrap()), "string" => Ok(value.into_string().unwrap().into_py_any(py).unwrap()), "char" => Ok(value .as_char() .unwrap() .to_string() .into_py_any(py) .unwrap()), "()" => 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.into_py_any(py)?.into()) } "map" => { let map = value.cast::(); let py_dict = PyDict::new(py); for (key, value) in map { let py_key = key.to_string().into_py_any(py).unwrap(); let py_value = dynamic_to_python(py, value)?; py_dict.set_item(py_key, py_value)?; } Ok(py_dict.into_py_any(py)?.into()) } _ => Err(PyRuntimeError::new_err(format!( "Unsupported Rhai type: {}", value.type_name() ))), } } /// Convert Python object to Rhai Dynamic fn python_to_dynamic(py: Python<'_>, obj: &Bound<'_, 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>, py_functions: std::collections::HashMap, } #[pymethods] impl RhaiEngine { #[new] fn new() -> Self { Self { engine: Engine::new(), scope: Scope::new(), py_functions: std::collections::HashMap::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: &Bound<'_, 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() } /// Register a Python function to be callable from Rhai using signature inspection fn register_fn(&mut self, py: Python<'_>, name: &str, func: PyObject) -> PyResult<()> { // Get the function signature using inspect.signature() let inspect_module = py.import("inspect")?; let signature_func = inspect_module.getattr("signature")?; let sig = signature_func.call1((&func,))?; let parameters = sig.getattr("parameters")?; let param_count = parameters.len()?; // Store the function self.py_functions .insert(name.to_string(), func.clone_ref(py)); let func_name = name.to_string(); // Register the function with the appropriate arity match param_count { 0 => { self.engine.register_fn(name, { let func = func.clone_ref(py); let name = func_name.clone(); move || -> Result> { Python::with_gil(|py| { let result = func .call0(py) .map_err(|e| format!("Python function '{}' error: {}", name, e))?; python_to_dynamic(py, &result.bind(py)) .map_err(|e| format!("Type conversion error: {}", e).into()) }) } }); } 1 => { self.engine.register_fn(name, { let func = func.clone_ref(py); let name = func_name.clone(); move |arg1: Dynamic| -> Result> { Python::with_gil(|py| { let py_arg1 = dynamic_to_python(py, arg1) .map_err(|e| format!("Type conversion error: {}", e))?; let result = func .call1(py, (py_arg1,)) .map_err(|e| format!("Python function '{}' error: {}", name, e))?; python_to_dynamic(py, &result.bind(py)) .map_err(|e| format!("Type conversion error: {}", e).into()) }) } }); } 2 => { self.engine.register_fn(name, { let func = func.clone_ref(py); let name = func_name.clone(); move |arg1: Dynamic, arg2: Dynamic| -> Result> { Python::with_gil(|py| { let py_arg1 = dynamic_to_python(py, arg1) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg2 = dynamic_to_python(py, arg2) .map_err(|e| format!("Type conversion error: {}", e))?; let result = func .call1(py, (py_arg1, py_arg2)) .map_err(|e| format!("Python function '{}' error: {}", name, e))?; python_to_dynamic(py, &result.bind(py)) .map_err(|e| format!("Type conversion error: {}", e).into()) }) } }); } 3 => { self.engine.register_fn(name, { let func = func.clone_ref(py); let name = func_name.clone(); move |arg1: Dynamic, arg2: Dynamic, arg3: Dynamic| -> Result> { Python::with_gil(|py| { let py_arg1 = dynamic_to_python(py, arg1) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg2 = dynamic_to_python(py, arg2) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg3 = dynamic_to_python(py, arg3) .map_err(|e| format!("Type conversion error: {}", e))?; let result = func .call1(py, (py_arg1, py_arg2, py_arg3)) .map_err(|e| format!("Python function '{}' error: {}", name, e))?; python_to_dynamic(py, &result.bind(py)) .map_err(|e| format!("Type conversion error: {}", e).into()) }) } }); } 4 => { self.engine.register_fn(name, { let func = func.clone_ref(py); let name = func_name.clone(); move |arg1: Dynamic, arg2: Dynamic, arg3: Dynamic, arg4: Dynamic| -> Result> { Python::with_gil(|py| { let py_arg1 = dynamic_to_python(py, arg1) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg2 = dynamic_to_python(py, arg2) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg3 = dynamic_to_python(py, arg3) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg4 = dynamic_to_python(py, arg4) .map_err(|e| format!("Type conversion error: {}", e))?; let result = func .call1(py, (py_arg1, py_arg2, py_arg3, py_arg4)) .map_err(|e| format!("Python function '{}' error: {}", name, e))?; python_to_dynamic(py, &result.bind(py)) .map_err(|e| format!("Type conversion error: {}", e).into()) }) } }); } 5 => { self.engine.register_fn(name, { let func = func.clone_ref(py); let name = func_name.clone(); move |arg1: Dynamic, arg2: Dynamic, arg3: Dynamic, arg4: Dynamic, arg5: Dynamic| -> Result> { Python::with_gil(|py| { let py_arg1 = dynamic_to_python(py, arg1) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg2 = dynamic_to_python(py, arg2) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg3 = dynamic_to_python(py, arg3) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg4 = dynamic_to_python(py, arg4) .map_err(|e| format!("Type conversion error: {}", e))?; let py_arg5 = dynamic_to_python(py, arg5) .map_err(|e| format!("Type conversion error: {}", e))?; let result = func .call1(py, (py_arg1, py_arg2, py_arg3, py_arg4, py_arg5)) .map_err(|e| format!("Python function '{}' error: {}", name, e))?; python_to_dynamic(py, &result.bind(py)) .map_err(|e| format!("Type conversion error: {}", e).into()) }) } }); } _ => { return Err(PyRuntimeError::new_err(format!( "Function '{}' has {} parameters, but only 0-5 parameters are supported", name, param_count ))); } } Ok(()) } /// List registered Python functions fn list_python_functions(&self) -> Vec { self.py_functions.keys().cloned().collect() } } /// A Python module implemented in Rust using PyO3 #[pymodule] fn rhai_python(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add("__version__", "0.1.1")?; Ok(()) }